Repository: cisagov/manage.get.gov

Issue:
Create DomainInformation and User role for transition domains
- Upon user login, in the same spot where we check if they have a domain invitation, Check if the domain on this domain invitation is from the transistion_domain. If yes, then:

   - create a Domain Information object that is mostly empty (just add the contact as creator) and add the domain name. 

   - Add a user role for this user as admin for this domain

- add a unit test for this scenario


note: when creating Domain information /User roles be sure to avoid duplication and do not throw an error if those already exist. 
Additional Context (optional)
https://docs.google.com/document/d/1UkQZdAmcGprXWdHX_PuT155CCNPbf_Gf/edit?pli=1#heading=h.iqv2v8lecjpn

Issue Links
Relates to: #927
Blocked by: #1055 


Discussion:
### Issue Description

Write a script which takes action when the following criterion is present:

Upon user login, in the same spot where we check if they have a domain invitation. Check if they also logged in using the email from the transistion_domain. If yes, then:
- create a Domain Information object that is mostly empty (just add the contact  as submitter) and add the domain name
- Add a user role for this user as admin for this domain

### Additional Context (optional)

https://docs.google.com/document/d/1UkQZdAmcGprXWdHX_PuT155CCNPbf_Gf/edit?pli=1#heading=h.iqv2v8lecjpn

### Issue Links

Relates to: #927 

Resolving patch:
diff --git a/src/registrar/models/user.py b/src/registrar/models/user.py
--- a/src/registrar/models/user.py
+++ b/src/registrar/models/user.py
@@ -4,6 +4,9 @@
 from django.db import models
 
 from .domain_invitation import DomainInvitation
+from .transition_domain import TransitionDomain
+from .domain_information import DomainInformation
+from .domain import Domain
 
 from phonenumber_field.modelfields import PhoneNumberField  # type: ignore
 
@@ -62,12 +65,9 @@ def unrestrict_user(self):
     def is_restricted(self):
         return self.status == self.RESTRICTED
 
-    def first_login(self):
-        """Callback when the user is authenticated for the very first time.
-
-        When a user first arrives on the site, we need to retrieve any domain
-        invitations that match their email address.
-        """
+    def check_domain_invitations_on_login(self):
+        """When a user first arrives on the site, we need to retrieve any domain
+        invitations that match their email address."""
         for invitation in DomainInvitation.objects.filter(
             email=self.email, status=DomainInvitation.INVITED
         ):
@@ -82,6 +82,101 @@ def first_login(self):
                     "Failed to retrieve invitation %s", invitation, exc_info=True
                 )
 
+    def create_domain_and_invite(self, transition_domain: TransitionDomain):
+        transition_domain_name = transition_domain.domain_name
+        transition_domain_status = transition_domain.status
+        transition_domain_email = transition_domain.username
+
+        # type safety check.  name should never be none
+        if transition_domain_name is not None:
+            new_domain = Domain(
+                name=transition_domain_name, state=transition_domain_status
+            )
+            new_domain.save()
+            # check that a domain invitation doesn't already
+            # exist for this e-mail / Domain pair
+            domain_email_already_in_domain_invites = DomainInvitation.objects.filter(
+                email=transition_domain_email.lower(), domain=new_domain
+            ).exists()
+            if not domain_email_already_in_domain_invites:
+                # Create new domain invitation
+                new_domain_invitation = DomainInvitation(
+                    email=transition_domain_email.lower(), domain=new_domain
+                )
+                new_domain_invitation.save()
+
+    def check_transition_domains_on_login(self):
+        """When a user first arrives on the site, we need to check
+        if they are logging in with the same e-mail as a
+        transition domain and update our database accordingly."""
+
+        for transition_domain in TransitionDomain.objects.filter(username=self.email):
+            # Looks like the user logged in with the same e-mail as
+            # one or more corresponding transition domains.
+            # Create corresponding DomainInformation objects.
+
+            # NOTE: adding an ADMIN user role for this user
+            # for each domain should already be done
+            # in the invitation.retrieve() method.
+            # However, if the migration scripts for transition
+            # domain objects were not executed correctly,
+            # there could be transition domains without
+            # any corresponding Domain & DomainInvitation objects,
+            # which means the invitation.retrieve() method might
+            # not execute.
+            # Check that there is a corresponding domain object
+            # for this transition domain.  If not, we have an error
+            # with our data and migrations need to be run again.
+
+            # Get the domain that corresponds with this transition domain
+            domain_exists = Domain.objects.filter(
+                name=transition_domain.domain_name
+            ).exists()
+            if not domain_exists:
+                logger.warn(
+                    """There are transition domains without
+                            corresponding domain objects!
+                            Please run migration scripts for transition domains
+                            (See data_migration.md)"""
+                )
+                # No need to throw an exception...just create a domain
+                # and domain invite, then proceed as normal
+                self.create_domain_and_invite(transition_domain)
+
+            domain = Domain.objects.get(name=transition_domain.domain_name)
+
+            # Create a domain information object, if one doesn't
+            # already exist
+            domain_info_exists = DomainInformation.objects.filter(
+                domain=domain
+            ).exists()
+            if not domain_info_exists:
+                new_domain_info = DomainInformation(creator=self, domain=domain)
+                new_domain_info.save()
+
+    def first_login(self):
+        """Callback when the user is authenticated for the very first time.
+
+        When a user first arrives on the site, we need to retrieve any domain
+        invitations that match their email address.
+
+        We also need to check if they are logging in with the same e-mail
+        as a transition domain and update our domainInfo objects accordingly.
+        """
+
+        # PART 1: TRANSITION DOMAINS
+        #
+        # NOTE: THIS MUST RUN FIRST
+        # (If we have an issue where transition domains were
+        # not fully converted into Domain and DomainInvitation
+        # objects, this method will fill in the gaps.
+        # This will ensure the Domain Invitations method
+        # runs correctly (no missing invites))
+        self.check_transition_domains_on_login()
+
+        # PART 2: DOMAIN INVITATIONS
+        self.check_domain_invitations_on_login()
+
     class Meta:
         permissions = [
             ("analyst_access_permission", "Analyst Access Permission"),


Test patch:
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
@@ -14,7 +14,8 @@
     UserDomainRole,
 )
 
-import boto3_mocking  # type: ignore
+import boto3_mocking
+from registrar.models.transition_domain import TransitionDomain  # type: ignore
 from .common import MockSESClient, less_console_noise, completed_application
 from django_fsm import TransitionNotAllowed
 
@@ -612,3 +613,48 @@ def test_retrieve_on_first_login(self):
         """A new user's first_login callback retrieves their invitations."""
         self.user.first_login()
         self.assertTrue(UserDomainRole.objects.get(user=self.user, domain=self.domain))
+
+
+class TestUser(TestCase):
+    """For now, just test actions that
+    occur on user login."""
+
+    def setUp(self):
+        self.email = "mayor@igorville.gov"
+        self.domain_name = "igorvilleInTransition.gov"
+        self.user, _ = User.objects.get_or_create(email=self.email)
+
+        # clean out the roles each time
+        UserDomainRole.objects.all().delete()
+
+        TransitionDomain.objects.get_or_create(
+            username="mayor@igorville.gov", domain_name=self.domain_name
+        )
+
+    def tearDown(self):
+        super().tearDown()
+        Domain.objects.all().delete()
+        DomainInvitation.objects.all().delete()
+        DomainInformation.objects.all().delete()
+        TransitionDomain.objects.all().delete()
+        User.objects.all().delete()
+
+    def test_check_transition_domains_on_login(self):
+        """A new user's first_login callback checks transition domains.
+        Makes DomainInformation object."""
+        self.domain, _ = Domain.objects.get_or_create(name=self.domain_name)
+
+        self.user.first_login()
+        self.assertTrue(DomainInformation.objects.get(domain=self.domain))
+
+    def test_check_transition_domains_without_domains_on_login(self):
+        """A new user's first_login callback checks transition domains.
+        This test makes sure that in the event a domain does not exist
+        for a given transition domain, both a domain and domain invitation
+        are created."""
+        self.user.first_login()
+        self.assertTrue(Domain.objects.get(name=self.domain_name))
+
+        domain = Domain.objects.get(name=self.domain_name)
+        self.assertTrue(DomainInvitation.objects.get(email=self.email, domain=domain))
+        self.assertTrue(DomainInformation.objects.get(domain=domain))