""" Test that various events are fired for models in the student app. """ from unittest import mock import pytest 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.tests.tests import UserSettingsEventTestMixin class TestUserProfileEvents(UserSettingsEventTestMixin, TestCase): """ Test that we emit field change events when UserProfile models are changed. """ def setUp(self): super().setUp() self.table = 'auth_userprofile' self.user = UserFactory.create() self.profile = self.user.profile self.reset_tracker() def test_change_one_field(self): """ Verify that we emit an event when a single field changes on the user profile. """ self.profile.year_of_birth = 1900 self.profile.save() self.assert_user_setting_event_emitted(setting='year_of_birth', old=None, new=self.profile.year_of_birth) # Verify that we remove the temporary `_changed_fields` property from # the model after we're done emitting events. with pytest.raises(AttributeError): self.profile._changed_fields # pylint: disable=pointless-statement, protected-access def test_change_many_fields(self): """ Verify that we emit one event per field when many fields change on the user profile in one transaction. """ self.profile.gender = 'o' self.profile.bio = 'test bio' self.profile.save() self.assert_user_setting_event_emitted(setting='bio', old=None, new=self.profile.bio) self.assert_user_setting_event_emitted(setting='gender', old='m', new='o') def test_unicode(self): """ Verify that the events we emit can handle unicode characters. """ old_name = self.profile.name self.profile.name = 'Dånîél' self.profile.save() self.assert_user_setting_event_emitted(setting='name', old=old_name, new=self.profile.name) def test_country(self): """ Verify that we properly serialize the JSON-unfriendly Country field. """ self.profile.country = Country('AL', 'dummy_flag_url') self.profile.save() self.assert_user_setting_event_emitted(setting='country', old=None, new=self.profile.country) def test_excluded_field(self): """ Verify that we don't emit events for ignored fields. """ self.profile.meta = {'foo': 'bar'} self.profile.save() self.assert_no_events_were_emitted() @mock.patch('common.djangoapps.student.models.UserProfile.save', side_effect=IntegrityError) def test_no_event_if_save_failed(self, _save_mock): """ Verify no event is triggered if the save does not complete. Note that the pre_save signal is not called in this case either, but the intent is to make it clear that this model should never emit an event if save fails. """ self.profile.gender = "unknown" with pytest.raises(IntegrityError): self.profile.save() self.assert_no_events_were_emitted() class TestUserEvents(UserSettingsEventTestMixin, TestCase): """ Test that we emit field change events when User models are changed. """ def setUp(self): super().setUp() self.user = UserFactory.create() self.reset_tracker() self.table = 'auth_user' def test_change_one_field(self): """ Verify that we emit an event when a single field changes on the user. """ old_username = self.user.username self.user.username = 'new username' self.user.save() self.assert_user_setting_event_emitted(setting='username', old=old_username, new=self.user.username) def test_change_many_fields(self): """ Verify that we emit one event per field when many fields change on the user in one transaction. """ old_email = self.user.email old_is_staff = self.user.is_staff self.user.email = 'foo@bar.com' self.user.is_staff = True self.user.save() self.assert_user_setting_event_emitted(setting='email', old=old_email, new=self.user.email) self.assert_user_setting_event_emitted(setting='is_staff', old=old_is_staff, new=self.user.is_staff) def test_password(self): """ Verify that password values are not included in the event payload. """ self.user.password = 'new password' self.user.save() self.assert_user_setting_event_emitted(setting='password', old=None, new=None) def test_related_fields_ignored(self): """ Verify that we don't emit events for related fields. """ self.user.loginfailures_set.create() self.user.save() self.assert_no_events_were_emitted() @mock.patch('django.contrib.auth.models.User.save', side_effect=IntegrityError) def test_no_event_if_save_failed(self, _save_mock): """ Verify no event is triggered if the save does not complete. Note that the pre_save signal is not called in this case either, but the intent is to make it clear that this model should never emit an event if save fails. """ self.user.password = 'new password' with pytest.raises(IntegrityError): self.user.save() self.assert_no_events_were_emitted() def test_no_first_and_last_name_events(self): """ Verify that first_name and last_name events are not emitted. """ self.user.first_name = "Donald" self.user.last_name = "Duck" self.user.save() self.assert_no_events_were_emitted() def test_enrolled_after_email_change(self): """ Test that when a user's email changes, the user is enrolled in pending courses. """ pending_enrollment = CourseEnrollmentAllowedFactory(auto_enroll=True) # lint-amnesty, pylint: disable=unused-variable # the e-mail will change to test@edx.org (from something else) assert self.user.email != 'test@edx.org' # there's a CEA for the new e-mail assert CourseEnrollmentAllowed.objects.count() == 1 assert CourseEnrollmentAllowed.objects.filter(email='test@edx.org').count() == 1 # Changing the e-mail to the enrollment-allowed e-mail should enroll self.user.email = 'test@edx.org' self.user.save() self.assert_user_enrollment_occurred('edX/toy/2012_Fall') # CEAs shouldn't have been affected assert CourseEnrollmentAllowed.objects.count() == 1 assert CourseEnrollmentAllowed.objects.filter(email='test@edx.org').count() == 1