Repository: communitybridge/easycla
Issue: Use GitHub User's Primary Email When Preparing DocuSign

## Summary

Use GitHub User's Primary Email When Preparing DocuSign

## Background

Brian Warner suggested that we should detect and use a GitHub contributor's primary GitHub email address when executing the ICLA DocuSign flow.  It was observed that his secondary email address was used to pre-set the ICLA Document within DocuSign.

## Expected behavior

- User's Primary GitHub email address is used to pre-set the ICLA Document within DocuSign

## Tasks

- [ ] Investigate if we can determine the user's primary GitHub email address
- [ ] If so, select the primary GitHub email address when preparing the DocuSign flow
- [ ] Test, validate, and demonstrate that the primary GitHub email address is indeed used.

## Screenshots

- N/A

## Environment

Please complete the following information:

* Environment:
  - [ ] DEV
  - [ ] STAGING
  - [X ] PROD 
* Browser:
  - [ ] Chrome/Brave
  - [ ] Firefox
  - [ ] Edge
  - [ ] Lynx
* Version: v1.0.35

## Additional Context

- User has primary and alternate email addresses configured in GitHub.

## Assumptions

If you are assuming certain facts or scenarios, list them so we have a baseline to understand your request.

- N/A

## Acceptance Criteria

The "done" criteria when this feature or problem is resolved. Such as:

1. Unit Tests added and running in CI
1. Functional Tests updated to cover feature, if applicable
1. Demonstrate the set of capabilities to the product team while the code is running in the DEV environment.

## References

- N/A




Changed files:
cla-backend/cla/tests/unit/test_user_models.py, cla-backend/cla/controllers/signing.py, cla-backend/cla/models/github_models.py, cla-backend/cla/tests/unit/test_user_event.py, cla-backend/cla/models/signing_service_interface.py, cla-backend/cla/models/dynamo_models.py, cla-backend/cla/models/docusign_models.py, cla-backend/cla/routes.py

diff --git a/cla-backend/cla/controllers/signing.py b/cla-backend/cla/controllers/signing.py
index 73d49a131..e62b5f5f7 100644
--- a/cla-backend/cla/controllers/signing.py
+++ b/cla-backend/cla/controllers/signing.py
@@ -9,13 +9,13 @@ import falcon
 
 import cla
 from cla.models import DoesNotExist
-from cla.utils import get_signing_service, get_signature_instance, get_email_service
+from cla.utils import get_signing_service, get_signature_instance, get_email_service, \\
+    get_supported_repository_providers, get_repository_service
 
 
-def request_individual_signature(project_id, user_id, return_url_type, return_url=None):
+def request_individual_signature(project_id, user_id, return_url_type, return_url=None, request=None):
     """
     Handle POST request to send ICLA signature request to user.
-
     :param project_id: The project to sign for.
     :type project_id: string
     :param user_id: The ID of the user that will sign.
@@ -24,12 +24,18 @@ def request_individual_signature(project_id, user_id, return_url_type, return_ur
     :type return_url_type: string
     :param return_url: The URL to return the user to after signing is complete.
     :type return_url: string
+    :param request: The Falcon Request object.
+    :type request: object
     """
     signing_service = get_signing_service()
     if return_url_type == "Gerrit":
         return signing_service.request_individual_signature_gerrit(str(project_id), str(user_id), return_url)
     elif return_url_type == "Github":
-        return signing_service.request_individual_signature(str(project_id), str(user_id), return_url)
+        # fetching the primary for the account
+        github = get_repository_service("github")
+        primary_user_email = github.get_primary_user_email(request)
+        return signing_service.request_individual_signature(str(project_id), str(user_id), return_url,
+                                                            preferred_email=primary_user_email)
 
 
 def request_corporate_signature(auth_user, project_id, company_id, send_as_email=False,
diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py
index 5d70a2c96..ad9cc0893 100644
--- a/cla-backend/cla/models/docusign_models.py
+++ b/cla-backend/cla/models/docusign_models.py
@@ -119,7 +119,8 @@ class DocuSign(signing_service_interface.SigningService):
         self.s3storage = S3Storage()
         self.s3storage.initialize(None)
 
-    def request_individual_signature(self, project_id, user_id, return_url=None):
+    def request_individual_signature(self, project_id, user_id, return_url=None, callback_url=None,
+                                     preferred_email=None):
         request_info = 'project: {project_id}, user: {user_id} with return_url: {return_url}'.format(
             project_id=project_id, user_id=user_id, return_url=return_url)
         cla.log.debug('Individual Signature - creating new signature for: {}'.format(request_info))
@@ -127,7 +128,7 @@ class DocuSign(signing_service_interface.SigningService):
         # Ensure this is a valid user
         user_id = str(user_id)
         try:
-            user = User()
+            user = User(preferred_email=preferred_email)
             user.load(user_id)
             cla.log.debug('Individual Signature - loaded user name: {}, '
                           'user email: {}, gh user: {}, gh id: {}'.
@@ -180,7 +181,8 @@ class DocuSign(signing_service_interface.SigningService):
                           format(latest_signature.get_signature_id()))
 
             # Re-generate and set the signing url - this will update the signature record
-            self.populate_sign_url(latest_signature, callback_url, default_values=default_cla_values)
+            self.populate_sign_url(latest_signature, callback_url, default_values=default_cla_values,
+                                   preferred_email=preferred_email)
 
             return {'user_id': user_id,
                     'project_id': project_id,
@@ -242,7 +244,8 @@ class DocuSign(signing_service_interface.SigningService):
         signature.set_signature_acl('github:{}'.format(user.get_user_github_id()))
 
         # Populate sign url
-        self.populate_sign_url(signature, callback_url, default_values=default_cla_values)
+        self.populate_sign_url(signature, callback_url, default_values=default_cla_values,
+                               preferred_email=preferred_email)
 
         # Save signature
         signature.save()
@@ -312,7 +315,7 @@ class DocuSign(signing_service_interface.SigningService):
                 return_url = gerrits[0].get_gerrit_url()
         except DoesNotExist as err:
             cla.log.error('Gerrit Instance not found by the given project ID: %s',
-                            project_id)
+                          project_id)
             return {'errors': {'project_id': str(err)}}
 
         try:
@@ -870,7 +873,8 @@ class DocuSign(signing_service_interface.SigningService):
                           authority_or_signatory_email=None,
                           send_as_email=False,
                           cla_manager_name=None, cla_manager_email=None,
-                          default_values: Optional[Dict[str, Any]] = None):  # pylint: disable=too-many-locals
+                          default_values: Optional[Dict[str, Any]] = None,
+                          preferred_email: str = None):  # pylint: disable=too-many-locals
 
         sig_type = signature.get_signature_reference_type()
 
@@ -885,7 +889,8 @@ class DocuSign(signing_service_interface.SigningService):
 
         # Depending on the signature type - we'll need either the company or the user record
         company = Company()
-        user = User()
+        #  by passing the preferred email we make sure the get_user_email will return it if present
+        user = User(preferred_email=preferred_email)
 
         # We use user name/email non-email docusign user ICLA
         user_signature_name = 'Unknown'
@@ -1748,7 +1753,7 @@ def create_default_company_values(company: Company,
     return values
 
 
-def create_default_individual_values(user: User) -> Dict[str, Any]:
+def create_default_individual_values(user: User, preferred_email: str = None) -> Dict[str, Any]:
     values = {}
 
     if user is None:
@@ -1758,7 +1763,7 @@ def create_default_individual_values(user: User) -> Dict[str, Any]:
         values['full_name'] = user.get_user_name()
         values['public_name'] = user.get_user_name()
 
-    if user.get_user_email() is not None:
+    if user.get_user_email(preferred_email=preferred_email) is not None:
         values['email'] = user.get_user_email()
 
     return values
diff --git a/cla-backend/cla/models/dynamo_models.py b/cla-backend/cla/models/dynamo_models.py
index 9efabb252..da916759b 100644
--- a/cla-backend/cla/models/dynamo_models.py
+++ b/cla-backend/cla/models/dynamo_models.py
@@ -1364,6 +1364,10 @@ class User(model_interfaces.User):  # pylint: disable=too-many-public-methods
             lf_sub=None,
             user_company_id=None,
             note=None,
+            # this is for cases when the user has more than one email (eg. github) and the get_email
+            # function is used in all over the places in legacy code. It's just not possible to introduce
+            # new functionality and not forget to update any of those references
+            preferred_email=None,
     ):
         super(User).__init__()
         self.model = UserModel()
@@ -1377,6 +1381,7 @@ class User(model_interfaces.User):  # pylint: disable=too-many-public-methods
         self.model.lf_sub = lf_sub
         self.model.user_company_id = user_company_id
         self.model.note = note
+        self._preferred_email = preferred_email
 
     def __str__(self):
         return (
@@ -1457,7 +1462,19 @@ class User(model_interfaces.User):  # pylint: disable=too-many-public-methods
     def get_lf_sub(self):
         return self.model.lf_sub
 
-    def get_user_email(self):
+    def get_user_email(self, preferred_email=None):
+        """
+        :param preferred_email: if the preferred email is in list of registered emails
+        it'd be returned, otherwise whatever email is present will be returned randomly
+        :return:
+        """
+        if preferred_email and self.model.lf_email is None and preferred_email == self.model.lf_email:
+            return preferred_email
+
+        preferred_email = preferred_email or self._preferred_email
+        if preferred_email and preferred_email in self.model.user_emails:
+            return preferred_email
+
         if self.model.lf_email is not None:
             return self.model.lf_email
         elif len(self.model.user_emails) > 0:
diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py
index 06cfecd6b..ffb4fd570 100644
--- a/cla-backend/cla/models/github_models.py
+++ b/cla-backend/cla/models/github_models.py
@@ -7,6 +7,7 @@ Holds the GitHub repository service.
 import json
 import os
 import uuid
+from typing import List, Union, Optional
 
 import falcon
 import github
@@ -494,7 +495,7 @@ class GitHub(repository_service_interface.RepositoryService):
             return {'error': 'Could not get user data: %s' % github_user['message']}
         return github_user
 
-    def get_user_emails(self, session, client_id):  # pylint: disable=no-self-use
+    def get_user_emails(self, session, client_id) -> Union[List[str], dict]:  # pylint: disable=no-self-use
         """
         Mockable method to get all user emails based on OAuth2 session.
 
@@ -503,15 +504,43 @@ class GitHub(repository_service_interface.RepositoryService):
         :param client_id: The GitHub OAuth2 client ID.
         :type client_id: string
         """
+        emails = self._fetch_github_emails(session=session, client_id=client_id)
+        cla.log.debug('GitHub user emails: %s', emails)
+        if 'error' in emails:
+            return emails
+        return [item['email'] for item in emails if item['verified']]
+
+    def get_primary_user_email(self, request) -> Union[Optional[str], dict]:
+        """
+        gets the user primary email from the registered emails from the github api
+        """
+        cla.log.debug("Fetching Github primary email")
+        session = self._get_request_session(request)
+        client_id = os.environ['GH_OAUTH_CLIENT_ID']
+        emails = self._fetch_github_emails(session=session, client_id=client_id)
+        if "error" in emails:
+            return None
+
+        for email in emails:
+            if email.get("verified", False) and email.get("primary", False):
+                return email["email"]
+        return None
+
+    def _fetch_github_emails(self, session, client_id) -> Union[List[dict], dict]:
+        """
+        Method is reponsible for fetching the user emails from /user/emails endpoint
+        :param session:
+        :param client_id:
+        :return:
+        """
         token = session['github_oauth2_token']
         oauth2 = OAuth2Session(client_id, token=token)
         request = oauth2.get('https://api.github.com/user/emails')
-        emails = request.json()
-        cla.log.debug('GitHub user emails: %s', emails)
-        if 'message' in emails:
-            cla.log.warning('Could not get user emails with OAuth2 token: %s', emails['message'])
-            return {'error': 'Could not get user emails: %s' % emails['message']}
-        return [item['email'] for item in emails if item['verified']]
+        resp = request.json()
+        if 'message' in resp:
+            cla.log.warning('Could not get user emails with OAuth2 token: %s', resp['message'])
+            return {'error': 'Could not get user emails: %s' % resp['message']}
+        return resp
 
     def process_reopened_pull_request(self, data):
         """
@@ -811,7 +840,8 @@ def has_check_previously_failed(pull_request: PullRequest) -> bool:
     return False
 
 
-def update_pull_request(installation_id, github_repository_id, pull_request, repository_name, signed, missing):  # pylint: disable=too-many-locals
+def update_pull_request(installation_id, github_repository_id, pull_request, repository_name, signed,
+                        missing):  # pylint: disable=too-many-locals
     """
     Helper function to update a PR's comment and status based on the list of signers.
 
diff --git a/cla-backend/cla/models/signing_service_interface.py b/cla-backend/cla/models/signing_service_interface.py
index 4768873b4..a9c52d21a 100644
--- a/cla-backend/cla/models/signing_service_interface.py
+++ b/cla-backend/cla/models/signing_service_interface.py
@@ -5,6 +5,7 @@
 Holds the signing service interfaces that all signing service models must implement.
 """
 
+
 class SigningService(object):
     """
     Interface to the signing service.
@@ -21,7 +22,8 @@ class SigningService(object):
         """
         raise NotImplementedError()
 
-    def request_individual_signature(self, project_id, user_id, return_url_type, return_url, callback_url=None):
+    def request_individual_signature(self, project_id, user_id, return_url_type, return_url, callback_url=None,
+                                     preferred_email=None):
         """
         Method that will request a new signature from the user.
 
@@ -39,6 +41,8 @@ class SigningService(object):
         :param callback_url: The URL that will be hit by the signing provider after successful
             signature from the user.
         :type callback_url: string
+        :param preferred_email: preferred email to use when creating signature
+        :type preferred_email: string
         :return: All data necessary to notify the user of the signing URL.
             Should return a dict of:
 
diff --git a/cla-backend/cla/routes.py b/cla-backend/cla/routes.py
index 966811347..365bf1186 100755
--- a/cla-backend/cla/routes.py
+++ b/cla-backend/cla/routes.py
@@ -1129,7 +1129,7 @@ def delete_project_document(
                         'user_id': 'some-user-uuid'}",
 )
 def request_individual_signature(
-        project_id: hug.types.uuid, user_id: hug.types.uuid, return_url_type=None, return_url=None,
+        request, project_id: hug.types.uuid, user_id: hug.types.uuid, return_url_type=None, return_url=None,
 ):
     """
     POST: /request-individual-signature
@@ -1152,7 +1152,8 @@ def request_individual_signature(
     User should hit the provided URL to initiate the signing process through the
     signing service provider.
     """
-    return cla.controllers.signing.request_individual_signature(project_id, user_id, return_url_type, return_url)
+    return cla.controllers.signing.request_individual_signature(project_id, user_id, return_url_type, return_url,
+                                                                request=request)
 
 
 @hug.post(
diff --git a/cla-backend/cla/tests/unit/test_user_event.py b/cla-backend/cla/tests/unit/test_user_event.py
index 821dfaa54..fe50ae9d8 100644
--- a/cla-backend/cla/tests/unit/test_user_event.py
+++ b/cla-backend/cla/tests/unit/test_user_event.py
@@ -2,6 +2,7 @@
 # SPDX-License-Identifier: MIT
 
 from unittest.mock import patch, Mock
+import unittest
 
 import pytest
 
@@ -16,98 +17,154 @@ def create_event_user():
     user_controller.create_event = Mock()
 
 
-@patch('cla.controllers.user.Event.create_event')
-def test_request_company_whitelist(mock_event, create_event_user, project, company, user):
-    """ Test user requesting to be added to the Approved List event """
-    event_type = EventType.RequestCompanyWL
-    User.load = Mock()
-    User.get_user_name = Mock(return_value=user.get_user_name())
-    User.get_user_emails = Mock(return_value=[user.get_user_email()])
-    User.get_user_email = Mock(return_value=user.get_user_email())
-    Company.load = Mock()
-    Company.get_company_name = Mock(return_value=company.get_company_name())
-    Project.load = Mock()
-    Project.get_project_name = Mock(return_value=project.get_project_name())
-    user_controller.get_email_service = Mock()
-    user_controller.send = Mock()
-    user_controller.request_company_whitelist(
-        user.get_user_id(),
-        company.get_company_id(),
-        user.get_user_name(),
-        user.get_user_email(),
-        project.get_project_id(),
-        message="Please add",
-        recipient_name="Recipient Name",
-        recipient_email="Recipient Email",
-    )
-
-    event_data = (f'CLA: contributor {user.get_user_name()} requests to be Approved for the '
-                  f'project: {project.get_project_name()} '
-                  f'organization: {company.get_company_name()} '
-                  f'as {user.get_user_name()} <{user.get_user_email()}>')
-
-    mock_event.assert_called_once_with(
-       event_user_id=user.get_user_id(),
-       event_project_id=project.get_project_id(),
-       event_company_id=company.get_company_id(),
-       event_type=event_type,
-       event_data=event_data,
-       event_summary=event_data,
-       contains_pii=True,
-    )
-
-
-@patch('cla.controllers.user.Event.create_event')
-def test_invite_cla_manager(mock_event, create_event_user, user):
-    """ Test send email to CLA manager event """
-    User.load = Mock()
-    Project.load_project_by_name = Mock()
-    CCLAWhitelistRequest.save = Mock()
-    user_controller.send_email_to_cla_manager = Mock()
-    contributor_id = user.get_user_id()
-    contributor_name = user.get_user_name()
-    contributor_email = user.get_user_email()
-    cla_manager_name = "admin"
-    cla_manager_email = "foo@admin.com"
-    project_name = "foo_project"
-    company_name = "Test Company"
-    event_data = (f'sent email to CLA Manager: {cla_manager_name} with email {cla_manager_email} '
-                  f'for project {project_name} and company {company_name} '
-                  f'to user {contributor_name} with email {contributor_email}')
-    # TODO FIX Unit test - need to mock Project load_project_by_name() function
-    user_controller.invite_cla_manager(contributor_id, contributor_name, contributor_email,
-                                      cla_manager_name, cla_manager_email,
-                                      project_name, company_name)
-    mock_event.assert_called_once_with(
-       event_user_id=contributor_id,
-       event_project_name=project_name,
-       event_data=event_data,
-       event_type=EventType.InviteAdmin,
-       event_summary=event_data,
-       contains_pii=True,
-    )
-
-
-@patch('cla.controllers.user.Event.create_event')
-def test_request_company_ccla(mock_event, create_event_user, user, project, company):
-    """ Test request company ccla event """
-    User.load = Mock()
-    User.get_user_name = Mock(return_value=user.get_user_name())
-    email = user.get_user_email()
-    Company.load = Mock()
-    Project.load = Mock()
-    Project.get_project_name = Mock(return_value=project.get_project_name())
-    manager = User(lf_username="harold", user_email="foo@gmail.com")
-    Company.get_managers = Mock(return_value=[manager, ])
-    event_data = f"Sent email to sign ccla for {project.get_project_name() }"
-    user_controller.request_company_ccla(
-        user.get_user_id(), email, company.get_company_id(), project.get_project_id()
-    )
-    mock_event.assert_called_once_with(
-        event_data=event_data,
-        event_summary=event_data,
-        event_type=EventType.RequestCCLA,
-        event_user_id=user.get_user_id(),
-        event_company_id=company.get_company_id(),
-        contains_pii=False,
-    )
+class TestRequestCompanyWhitelist:
+
+    def setup(self) -> None:
+        self.old_load = User.load
+        self.old_get_user_name = User.get_user_name
+        self.get_user_emails = User.get_user_emails
+        self.get_user_email = User.get_user_email
+
+        self.company_load = Company.load
+        self.get_company_name = Company.get_company_name
+
+        self.project_load = Project.load
+        self.get_project_name = Project.get_project_name
+
+    def teardown(self) -> None:
+        User.load = self.old_load
+        User.get_user_name = self.old_get_user_name
+        User.get_user_emails = self.get_user_emails
+        User.get_user_email = self.get_user_email
+
+        Company.load = self.company_load
+        Company.get_company_name = self.get_company_name
+
+        Project.load = self.project_load
+        Project.get_project_name = self.get_project_name
+
+    def test_request_company_whitelist(self, create_event_user, project, company, user):
+        """ Test user requesting to be added to the Approved List event """
+        with patch('cla.controllers.user.Event.create_event') as mock_event:
+            event_type = EventType.RequestCompanyWL
+            User.load = Mock()
+            User.get_user_name = Mock(return_value=user.get_user_name())
+            User.get_user_emails = Mock(return_value=[user.get_user_email()])
+            User.get_user_email = Mock(return_value=user.get_user_email())
+            Company.load = Mock()
+            Company.get_company_name = Mock(return_value=company.get_company_name())
+            Project.load = Mock()
+            Project.get_project_name = Mock(return_value=project.get_project_name())
+            user_controller.get_email_service = Mock()
+            user_controller.send = Mock()
+            user_controller.request_company_whitelist(
+                user.get_user_id(),
+                company.get_company_id(),
+                user.get_user_name(),
+                user.get_user_email(),
+                project.get_project_id(),
+                message="Please add",
+                recipient_name="Recipient Name",
+                recipient_email="Recipient Email",
+            )
+
+            event_data = (f'CLA: contributor {user.get_user_name()} requests to be Approved for the '
+                          f'project: {project.get_project_name()} '
+                          f'organization: {company.get_company_name()} '
+                          f'as {user.get_user_name()} <{user.get_user_email()}>')
+
+            mock_event.assert_called_once_with(
+                event_user_id=user.get_user_id(),
+                event_project_id=project.get_project_id(),
+                event_company_id=company.get_company_id(),
+                event_type=event_type,
+                event_data=event_data,
+                event_summary=event_data,
+                contains_pii=True,
+            )
+
+
+class TestInviteClaManager:
+
+    def setup(self):
+        self.user_load = User.load
+        self.load_project_by_name = Project.load_project_by_name
+        self.save = CCLAWhitelistRequest.save
+
+    def teardown(self):
+        User.load = self.user_load
+        Project.load_project_by_name = self.load_project_by_name
+        CCLAWhitelistRequest.save = self.save
+
+    @patch('cla.controllers.user.Event.create_event')
+    def test_invite_cla_manager(self, mock_event, create_event_user, user):
+        """ Test send email to CLA manager event """
+        User.load = Mock()
+        Project.load_project_by_name = Mock()
+        CCLAWhitelistRequest.save = Mock()
+        user_controller.send_email_to_cla_manager = Mock()
+        contributor_id = user.get_user_id()
+        contributor_name = user.get_user_name()
+        contributor_email = user.get_user_email()
+        cla_manager_name = "admin"
+        cla_manager_email = "foo@admin.com"
+        project_name = "foo_project"
+        company_name = "Test Company"
+        event_data = (f'sent email to CLA Manager: {cla_manager_name} with email {cla_manager_email} '
+                      f'for project {project_name} and company {company_name} '
+                      f'to user {contributor_name} with email {contributor_email}')
+        # TODO FIX Unit test - need to mock Project load_project_by_name() function
+        user_controller.invite_cla_manager(contributor_id, contributor_name, contributor_email,
+                                           cla_manager_name, cla_manager_email,
+                                           project_name, company_name)
+        mock_event.assert_called_once_with(
+            event_user_id=contributor_id,
+            event_project_name=project_name,
+            event_data=event_data,
+            event_type=EventType.InviteAdmin,
+            event_summary=event_data,
+            contains_pii=True,
+        )
+
+
+class TestRequestCompanyCCLA:
+
+    def setup(self):
+        self.user_load = User.load
+        self.get_user_name = User.get_user_name
+        self.company_load = Company.load
+        self.project_load = Project.load
+        self.get_project_name = Project.get_project_name
+        self.get_managers = Company.get_managers
+
+    def teardown(self):
+        User.load = self.user_load
+        User.get_user_name = self.get_user_name
+        Company.load = self.company_load
+        Project.load = self.project_load
+        Project.get_project_name = self.get_project_name
+        Company.get_managers = self.get_managers
+
+    @patch('cla.controllers.user.Event.create_event')
+    def test_request_company_ccla(self, mock_event, create_event_user, user, project, company):
+        """ Test request company ccla event """
+        User.load = Mock()
+        User.get_user_name = Mock(return_value=user.get_user_name())
+        email = user.get_user_email()
+        Company.load = Mock()
+        Project.load = Mock()
+        Project.get_project_name = Mock(return_value=project.get_project_name())
+        manager = User(lf_username="harold", user_email="foo@gmail.com")
+        Company.get_managers = Mock(return_value=[manager, ])
+        event_data = f"Sent email to sign ccla for {project.get_project_name()}"
+        user_controller.request_company_ccla(
+            user.get_user_id(), email, company.get_company_id(), project.get_project_id()
+        )
+        mock_event.assert_called_once_with(
+            event_data=event_data,
+            event_summary=event_data,
+            event_type=EventType.RequestCCLA,
+            event_user_id=user.get_user_id(),
+            event_company_id=company.get_company_id(),
+            contains_pii=False,
+        )
diff --git a/cla-backend/cla/tests/unit/test_user_models.py b/cla-backend/cla/tests/unit/test_user_models.py
index 32f0a534a..80baceb72 100644
--- a/cla-backend/cla/tests/unit/test_user_models.py
+++ b/cla-backend/cla/tests/unit/test_user_models.py
@@ -83,6 +83,36 @@ class TestUserModels(unittest.TestCase):
             users = User().get_user_by_github_username('foooooo')
             self.assertIsNone(users, 'User lookup by github username is None')
 
+    def test_get_user_email(self):
+        user = User()
+        user.set_lf_email(None)
+        user.set_user_emails([])
+        assert user.get_user_email() is None
+
+        user.set_lf_email("test1@test.com")
+        assert user.get_user_email() == "test1@test.com"
+
+        user = User(user_email="test2@test.com")
+        assert user.get_user_email() == "test2@test.com"
+
+        user = User(user_email="test3@test.com", preferred_email="test3@test.com")
+        assert user.get_user_email() == "test3@test.com"
+        user.set_user_emails(["test4@test.com", "test5@test.com"])
+        user.set_lf_email("test3@test.com")
+        assert user.get_user_email() == "test3@test.com"
+
+        # the scenario where have multiple emails
+        user = User(preferred_email="test5@test.com")
+        user.set_user_emails(["test1@test.com", "test2@test.com", "test5@test.com"])
+        assert user.get_user_email() == "test5@test.com"
+        assert user.get_user_email(preferred_email="test2@test.com") == "test2@test.com"
+        assert user.get_user_email(preferred_email="test10@test.com") != "test10@test.com"
+        user.set_lf_email("test4@test.com")
+        assert user.get_user_email() == "test5@test.com"
+        assert user.get_user_email(preferred_email="test4@test.com") == "test4@test.com"
+        assert user.get_user_email(preferred_email="test2@test.com") == "test2@test.com"
+        assert user.get_user_email(preferred_email="test10@test.com") == "test4@test.com"
+
 
 if __name__ == '__main__':
     unittest.main()