Repository: e-valuation/EvaP

Issue:
Missing form error for duplicate email address
When changing a user's email address to an address that does not yet exist, but results in an existing address after applying the replacements in `evaluation.tools.clean_email`, an internal server error is thrown.

`staff.forms.clean_email` should call `evaluation.tools.clean_email` to prevent this.

Test case:
- replace the `INSTITUTION_EMAIL_REPLACEMENTS` definition in the settings to `INSTITUTION_EMAIL_REPLACEMENTS: List[Tuple[str, str]] = [("institution.example.com", "institution.com")]`
- change Oma Abner's email address to `oma.abner@institution.com`
- change any other user's email address to `oma.abner@institution.example.com`


Discussion:

Resolving patch:
diff --git a/evap/staff/forms.py b/evap/staff/forms.py
--- a/evap/staff/forms.py
+++ b/evap/staff/forms.py
@@ -31,7 +31,7 @@
     TextAnswer,
     UserProfile,
 )
-from evap.evaluation.tools import date_to_datetime
+from evap.evaluation.tools import clean_email, date_to_datetime
 from evap.results.tools import STATES_WITH_RESULT_TEMPLATE_CACHING, STATES_WITH_RESULTS_CACHING, cache_results
 from evap.results.views import update_template_cache, update_template_cache_of_published_evaluations_in_course
 from evap.staff.tools import remove_user_from_represented_and_ccing_users
@@ -987,7 +987,7 @@ def clean_evaluations_participating_in(self):
         return evaluations_participating_in
 
     def clean_email(self):
-        email = self.cleaned_data.get("email")
+        email = clean_email(self.cleaned_data.get("email"))
         if email is None:
             return None
 
@@ -999,7 +999,7 @@ def clean_email(self):
 
         if user_with_same_email.exists():
             raise forms.ValidationError(_("A user with the email '%s' already exists") % email)
-        return email.lower()
+        return email
 
     def save(self, *args, **kw):
         super().save(*args, **kw)


Test patch:
diff --git a/evap/staff/tests/test_forms.py b/evap/staff/tests/test_forms.py
--- a/evap/staff/tests/test_forms.py
+++ b/evap/staff/tests/test_forms.py
@@ -1,7 +1,7 @@
 from unittest.mock import patch
 
 from django.forms.models import inlineformset_factory
-from django.test import TestCase
+from django.test import TestCase, override_settings
 from model_bakery import baker
 
 from evap.contributor.forms import EvaluationForm as ContributorEvaluationForm
@@ -121,6 +121,10 @@ def test_evaluation_email_form(self):
 
 
 class UserFormTests(TestCase):
+    @classmethod
+    def setUpTestData(cls):
+        cls.existing_user = baker.make(UserProfile, email="existing@example.com")
+
     def test_user_form(self):
         """
         Tests the UserForm with one valid and one invalid input dataset.
@@ -135,6 +139,7 @@ def test_user_form(self):
         form = UserForm(instance=user, data=data)
         self.assertFalse(form.is_valid())
 
+    @override_settings(INSTITUTION_EMAIL_REPLACEMENTS=[("institution.example.com", "example.com")])
     def test_user_with_same_email(self):
         """
         Tests whether the user form correctly handles email adresses
@@ -155,6 +160,11 @@ def test_user_with_same_email(self):
         form = UserForm(instance=user, data=data)
         self.assertTrue(form.is_valid())
 
+        data = {"email": "existing@institution.example.com"}
+        form = UserForm(instance=user, data=data)
+        self.assertFalse(form.is_valid())
+        self.assertIn("A user with the email 'existing@example.com' already exists", form.errors["email"])
+
     def test_user_cannot_be_removed_from_evaluation_already_voted_for(self):
         student = baker.make(UserProfile)
         baker.make(Evaluation, participants=[student], voters=[student], course__semester__is_active=True)