Repository: cisagov/manage.get.gov

Issue:
User/Contact bug: Signals double saving, overwriting data for test cases
### Current Behavior

A bug was introduced [with a PR](https://github.com/cisagov/manage.get.gov/pull/1491) that fixed our oidc login clearing out user information. To fix this, the Contact object was linked to the User object, such that they would remain in sync.

However, this introduced a sneaky double-save bug wherein if signals get called more than once for the same contact object (as we found happens in our test cases), it overrides information when it should not do so.

![image](https://github.com/cisagov/manage.get.gov/assets/141044360/f9d35949-5138-4337-a189-9e2a843191f5)
![image](https://github.com/cisagov/manage.get.gov/assets/141044360/917e6db7-6cfd-48c8-a9b0-e0b8f01f2831)

### Expected Behavior

When a `Contact` object is saved, the `save()` function checks to see if `self.user` is not None. When it is not, it will update the  `self.user` object with whatever value is located at `self`. It appears that in certain situations, (such as our test cases),  the `User` field is behaving as if it were a one-to-many relationship (one Contact object to many User objects). This should not be the case, and `Contact` should only update one `User` field.

### Steps to Reproduce

1. Create a new test case in `test_admin.py`, and create 3 fake User objects. Populate them with unique data. Then, log their values. Note that their data seems to get overwritten.


### Environment

_No response_

### Additional Context

This bug seems to be originating in the `signals.py` file in the ` handle_profile` class. By passing in a flag that disables the save behavior (or just commenting it out), this issue seems to resolve. 
![image](https://github.com/cisagov/manage.get.gov/assets/141044360/e19a1508-72c7-405a-96db-3540305dcc4b)


### Issue Links

🔄 Relates to: [#1464 / #1468](https://github.com/cisagov/manage.get.gov/pull/1491)
🔄 Relates to: [this PR](https://github.com/cisagov/manage.get.gov/pull/1543) (pinpoints where the behavior is occurring but does not solve it)


Discussion:

Resolving patch:
diff --git a/src/registrar/models/contact.py b/src/registrar/models/contact.py
--- a/src/registrar/models/contact.py
+++ b/src/registrar/models/contact.py
@@ -64,7 +64,7 @@ def save(self, *args, **kwargs):
         super().save(*args, **kwargs)
 
         # Update the related User object's first_name and last_name
-        if self.user:
+        if self.user and (not self.user.first_name or not self.user.last_name):
             self.user.first_name = self.first_name
             self.user.last_name = self.last_name
             self.user.save()


Test patch:
diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py
--- a/src/registrar/tests/test_admin.py
+++ b/src/registrar/tests/test_admin.py
@@ -1110,8 +1110,8 @@ def test_alphabetically_sorted_fk_fields_domain_application(self):
         tested_fields = [
             DomainApplication.authorizing_official.field,
             DomainApplication.submitter.field,
-            # DomainApplication.investigator.field,
-            # DomainApplication.creator.field,
+            DomainApplication.investigator.field,
+            DomainApplication.creator.field,
             DomainApplication.requested_domain.field,
         ]
 
diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py
--- a/src/registrar/tests/test_models.py
+++ b/src/registrar/tests/test_models.py
@@ -672,6 +672,12 @@ def test_check_domain_invitations_on_login_caps_email(self):
 
 class TestContact(TestCase):
     def setUp(self):
+        self.email_for_invalid = "intern@igorville.gov"
+        self.invalid_user, _ = User.objects.get_or_create(
+            username=self.email_for_invalid, email=self.email_for_invalid, first_name="", last_name=""
+        )
+        self.invalid_contact, _ = Contact.objects.get_or_create(user=self.invalid_user)
+
         self.email = "mayor@igorville.gov"
         self.user, _ = User.objects.get_or_create(email=self.email, first_name="Jeff", last_name="Lebowski")
         self.contact, _ = Contact.objects.get_or_create(user=self.user)
@@ -683,6 +689,31 @@ def tearDown(self):
 
     def test_saving_contact_updates_user_first_last_names(self):
         """When a contact is updated, we propagate the changes to the linked user if it exists."""
+
+        # User and Contact are created and linked as expected.
+        # An empty User object should create an empty contact.
+        self.assertEqual(self.invalid_contact.first_name, "")
+        self.assertEqual(self.invalid_contact.last_name, "")
+        self.assertEqual(self.invalid_user.first_name, "")
+        self.assertEqual(self.invalid_user.last_name, "")
+
+        # Manually update the contact - mimicking production (pre-existing data)
+        self.invalid_contact.first_name = "Joey"
+        self.invalid_contact.last_name = "Baloney"
+        self.invalid_contact.save()
+
+        # Refresh the user object to reflect the changes made in the database
+        self.invalid_user.refresh_from_db()
+
+        # Updating the contact's first and last names propagate to the user
+        self.assertEqual(self.invalid_contact.first_name, "Joey")
+        self.assertEqual(self.invalid_contact.last_name, "Baloney")
+        self.assertEqual(self.invalid_user.first_name, "Joey")
+        self.assertEqual(self.invalid_user.last_name, "Baloney")
+
+    def test_saving_contact_does_not_update_user_first_last_names(self):
+        """When a contact is updated, we avoid propagating the changes to the linked user if it already has a value"""
+
         # User and Contact are created and linked as expected
         self.assertEqual(self.contact.first_name, "Jeff")
         self.assertEqual(self.contact.last_name, "Lebowski")
@@ -699,11 +730,11 @@ def test_saving_contact_updates_user_first_last_names(self):
         # Updating the contact's first and last names propagate to the user
         self.assertEqual(self.contact.first_name, "Joey")
         self.assertEqual(self.contact.last_name, "Baloney")
-        self.assertEqual(self.user.first_name, "Joey")
-        self.assertEqual(self.user.last_name, "Baloney")
+        self.assertEqual(self.user.first_name, "Jeff")
+        self.assertEqual(self.user.last_name, "Lebowski")
 
     def test_saving_contact_does_not_update_user_email(self):
-        """When a contact's email is updated, the change is not propagated to the lined user."""
+        """When a contact's email is updated, the change is not propagated to the user."""
         self.contact.email = "joey.baloney@diaperville.com"
         self.contact.save()
 
@@ -713,3 +744,16 @@ def test_saving_contact_does_not_update_user_email(self):
         # Updating the contact's email does not propagate
         self.assertEqual(self.contact.email, "joey.baloney@diaperville.com")
         self.assertEqual(self.user.email, "mayor@igorville.gov")
+
+    def test_saving_contact_does_not_update_user_email_when_none(self):
+        """When a contact's email is updated, and the first/last name is none,
+        the change is not propagated to the user."""
+        self.invalid_contact.email = "joey.baloney@diaperville.com"
+        self.invalid_contact.save()
+
+        # Refresh the user object to reflect the changes made in the database
+        self.invalid_user.refresh_from_db()
+
+        # Updating the contact's email does not propagate
+        self.assertEqual(self.invalid_contact.email, "joey.baloney@diaperville.com")
+        self.assertEqual(self.invalid_user.email, "intern@igorville.gov")
diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py
--- a/src/registrar/tests/test_models_domain.py
+++ b/src/registrar/tests/test_models_domain.py
@@ -761,7 +761,6 @@ def test_not_disclosed_on_other_contacts(self):
             self.assertEqual(expected_contact.email, actual_contact.email)
 
     def test_convert_public_contact_to_epp(self):
-        self.maxDiff = None
         domain, _ = Domain.objects.get_or_create(name="freeman.gov")
         dummy_contact = domain.get_default_security_contact()
         test_disclose = self._convertPublicContactToEpp(dummy_contact, disclose_email=True).__dict__
diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py
--- a/src/registrar/tests/test_reports.py
+++ b/src/registrar/tests/test_reports.py
@@ -152,7 +152,6 @@ def side_effect(Bucket, Key):
     @boto3_mocking.patching
     def test_load_federal_report(self):
         """Tests the get_current_federal api endpoint"""
-        self.maxDiff = None
         mock_client = MagicMock()
         mock_client_instance = mock_client.return_value