Repository: box/box-python-sdk
Pull request #334 (2018-08-22 00:18:59)

User v2

coveralls: ## Pull Request Test Coverage Report for [Build 1179](https://coveralls.io/builds/19624651)

* **44** of **52**   **(84.62%)**  changed or added relevant lines in **5** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.2%**) to **95.03%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [boxsdk/object/user.py](https://coveralls.io/builds/19624651/source?filename=boxsdk%2Fobject%2Fuser.py#L83) | 24 | 27 | 88.89%
| [boxsdk/client/client.py](https://coveralls.io/builds/19624651/source?filename=boxsdk%2Fclient%2Fclient.py#L154) | 5 | 10 | 50.0%
<!-- | **Total:** | **44** | **52** | **84.62%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/19624651/badge)](https://coveralls.io/builds/19624651) |
| :-- | --: |
| Change from base [Build 1148](https://coveralls.io/builds/19605860): |  -0.2% |
| Covered Lines: | 2199 |
| Relevant Lines: | 2314 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/boxsdk/client/client.py b/boxsdk/client/client.py
index 2a2930b..2a6cd57 100644
--- a/boxsdk/client/client.py
+++ b/boxsdk/client/client.py
@@ -1,6 +1,7 @@
 # coding: utf-8
 # pylint: disable=too-many-lines
 from __future__ import unicode_literals, absolute_import
+
 import json
 
 from ..auth.oauth2 import TokenResponse
@@ -152,6 +153,36 @@ def user(self, user_id='me'):
         """
         return self.translator.get('user')(session=self._session, object_id=user_id)
 
+    def invite(self, invite_id):
+        """
+        Initialize a :class:`Invite` object, whose box id is invite_id.
+
+        :param invite_id:
+            The invite ID of the :class:`Invite` object.
+        :type invite_id:
+            `unicode`
+        :return:
+            A :class:`Invite` object with the given entry ID.
+        :rtype:
+            :class:`Invite`
+        """
+        return self.translator.get('invite')(session=self._session, object_id=invite_id)
+
+    def email_alias(self, alias_id):
+        """
+        Initialize a :class: `EmailAlias` object, whose box id is alias_id.
+
+        :param alias_id:
+            The aliad id of the :class:`EmailAlias` object.
+        :type alias_id:
+            `unicode`
+        :return:
+            A :class:`EmailAlias` object with the given entry ID.
+        :rtype:
+            :class:`EmailAlias`
+        """
+        return self.translator.get('email_alias')(session=self._session, object_id=alias_id)
+
     def group(self, group_id):
         """
         Initialize a :class:`Group` object, whose box id is group_id.
@@ -426,6 +457,23 @@ def enterprise(self, enterprise_id):
         """
         return self.translator.get('enterprise')(session=self._session, object_id=enterprise_id)
 
+    @api_call
+    def get_current_enterprise(self):
+        """
+        Get the enterprise of the current user.
+
+        :returns:
+            The authenticated user's enterprise
+        :rtype:
+            :class:`Enterprise`
+        """
+        user = self.user().get(fields=['enterprise'])
+        enterprise_object = user['enterprise']
+        return self.translator.translate(
+            session=self._session,
+            response_object=enterprise_object,
+        )
+
     @api_call
     def users(self, limit=None, offset=0, filter_term=None, user_type=None, fields=None):
         """
diff --git a/boxsdk/object/email_alias.py b/boxsdk/object/email_alias.py
new file mode 100644
index 0000000..6ff3221
--- /dev/null
+++ b/boxsdk/object/email_alias.py
@@ -0,0 +1,10 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+from .base_object import BaseObject
+
+
+class EmailAlias(BaseObject):
+    """Represents a Box email alias."""
+
+    _item_type = 'email_alias'
diff --git a/boxsdk/object/enterprise.py b/boxsdk/object/enterprise.py
index 16a7a9a..a6dd584 100644
--- a/boxsdk/object/enterprise.py
+++ b/boxsdk/object/enterprise.py
@@ -1,10 +1,42 @@
 # coding: utf-8
 from __future__ import unicode_literals, absolute_import
 
+import json
+
 from .base_object import BaseObject
+from ..util.api_call_decorator import api_call
 
 
 class Enterprise(BaseObject):
 
     """Represents a Box file version retention."""
     _item_type = 'enterprise'
+
+    @api_call
+    def invite_user(self, user_email):
+        """
+        Invites an existing user to an Enterprise. The user must already have a Box account.
+
+        :param user_email:
+            The login email address of the user that will receive the invitation.
+        :type user_email:
+            `unicode`
+        :returns:
+            The invitation record for the user
+        :rtype:
+            :class:`Invite`
+        """
+        url = self._session.get_url('invites')
+        body = {
+            'enterprise': {
+                'id': self.object_id,
+            },
+            'actionable_by': {
+                'login': user_email,
+            },
+        }
+        response = self._session.post(url, data=json.dumps(body)).json()
+        return self.translator.translate(
+            session=self._session,
+            response_object=response,
+        )
diff --git a/boxsdk/object/invite.py b/boxsdk/object/invite.py
new file mode 100644
index 0000000..7e7f6c1
--- /dev/null
+++ b/boxsdk/object/invite.py
@@ -0,0 +1,9 @@
+# coding:utf-8
+from __future__ import unicode_literals, absolute_import
+from .base_object import BaseObject
+
+
+class Invite(BaseObject):
+    """Represents the invite"""
+
+    _item_type = 'invite'
diff --git a/boxsdk/object/user.py b/boxsdk/object/user.py
index e1d576e..5e4937d 100644
--- a/boxsdk/object/user.py
+++ b/boxsdk/object/user.py
@@ -1,9 +1,11 @@
 # coding: utf-8
-
 from __future__ import unicode_literals
 
+import json
+
 from .base_object import BaseObject
 from ..pagination.limit_offset_based_object_collection import LimitOffsetBasedObjectCollection
+from ..pagination.marker_based_object_collection import MarkerBasedObjectCollection
 from ..util.api_call_decorator import api_call
 
 
@@ -12,6 +14,114 @@ class User(BaseObject):
 
     _item_type = 'user'
 
+    @api_call
+    def add_email_alias(self, email):
+        """
+        Adds a new email alias to the given user's account.
+
+        :param email:
+            The email alias to add to the user.
+        :type email:
+            `unicode`
+        :returns:
+            The new email alias object
+        :rtype:
+            :class:`EmailAlias`
+        """
+        url = self.get_url('email_aliases')
+        body = {
+            'email': email,
+        }
+        response = self._session.post(url, data=json.dumps(body)).json()
+        return self.translator.translate(
+            session=self._session,
+            response_object=response,
+        )
+
+    @api_call
+    def get_email_aliases(self, limit=None, fields=None):
+        """
+        Gets an list of email aliases for a user.
+
+        :param limit:
+            The maximum number of users to return. If not specified, the Box API will determine an appropriate limit.
+        :type limit:
+            `int` or None
+        :param fields:
+            List of fields to request
+        :type fields:
+            `Iterable` of `unicode`
+        :returns:
+            An iterator of the user's email aliases
+        :rtype:
+            :class:`BoxObjectCollection`
+        """
+        return MarkerBasedObjectCollection(
+            session=self._session,
+            url=self.get_url('email_aliases'),
+            limit=limit,
+            marker=None,
+            fields=fields,
+            return_full_pages=False,
+        )
+
+    @api_call
+    def remove_email_alias(self, email_alias):
+        """
+        Remove an email alias from the user.
+
+        :param email_alias:
+            The email alias to remove.
+        :type email_alias:
+            :class:`EmailAlias`
+        :returns:
+            Whether the removal succeeded.
+        :rtype:
+            `bool`
+        """
+        url = self.get_url('email_aliases', email_alias.object_id)
+        response = self._session.delete(url, expect_json_response=False)
+        return response.ok
+
+    @api_call
+    def transfer_content(self, destination_user, notify=None, fields=None):
+        """
+        Move all of the items owned by a user into a new folder in another user's account.
+
+        :param destination_user:
+            The id of the user to transfer content to.
+        :type destination_user:
+            :class:`User`
+        :param notify:
+            Whether the destination user should receive email notification of the transfer.
+        :type notify:
+            `bool` or None
+        :param fields:
+            Fields to return on the resulting :class:`Folder` object
+        :type fields:
+            `Iterable` of `unicode`
+        :returns:
+            A :class:`Folder` object that was transferred to another user.
+        :rtype:
+            :class:`Folder`
+        """
+        url = self.get_url('folders', '0')
+        body = {
+            'owned_by': {
+                'id': destination_user.object_id,
+            },
+        }
+        params = {}
+        if notify is not None:
+            params['notify'] = notify
+        if fields is not None:
+            params['fields'] = ','.join(fields)
+        response = self._session.put(url, data=json.dumps(body), params=params).json()
+        return self.translator.translate(
+            session=self._session,
+            response_object=response,
+        )
+
     def get_storage_policy_assignment(self):
         """
         Get the storage policy assignment assigned to the user.
@@ -66,3 +176,26 @@ def get_group_memberships(self, limit=None, offset=None, fields=None):
             offset=offset,
             return_full_pages=False,
         )
+
+    @api_call
+    def delete(self, notify=True, force=False):
+        # pylint: disable=arguments-differ
+        """
+        Delete a user's account.  This user will no longer be able to access Box.
+
+        :param notify:
+            Whether a notification should be sent about the deletion
+        :type notify:
+            `bool`
+        :param force:
+            Whether the user should be deleted even if they still own files
+        :returns:
+            Whether the deletion succeeded
+        :rtype:
+            `bool`
+        """
+        params = {
+            'notify': notify,
+            'force': force,
+        }
+        return super(User, self).delete(params=params)
diff --git a/docs/user.md b/docs/user.md
new file mode 100644
index 0000000..2c351a1
--- /dev/null
+++ b/docs/user.md
@@ -0,0 +1,221 @@
+Users
+=====
+
+Users represent an individual's account on Box.
+
+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+
+
+- [Get User Information](#get-user-information)
+- [Get the Current User's Information](#get-the-current-users-information)
+- [Create An Enterprise User](#create-an-enterprise-user)
+- [Create An App User](#create-an-app-user)
+- [Update User](#update-user)
+- [Delete User](#delete-user)
+- [Invite User to Enterprise](#invite-user-to-enterprise)
+- [Get Email Aliases](#get-email-aliases)
+- [Add Email Alias](#add-email-alias)
+- [Remove Email Alias](#remove-email-alias)
+- [Get Enterprise Users](#get-enterprise-users)
+- [Transfer User Content](#transfer-user-content)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+Get User Information
+--------------------
+
+To get information about a user, call the [`user.get(fields=None)`][object_get] method.  This method returns a new
+[`User`][user_class] object with fields populated by data from the API.
+
+```python
+user_id = '33333'
+user = client.user(user_id).get()
+```
+
+You can specify which fields on the `User` object you want by passing an `Iterable` of field names:
+
+```python
+user_id = '33333'
+user = client.user(user_id).get(['id', 'name', 'login', 'is_sync_enabled'])
+
+if user.is_sync_enabled:
+    print('User {0} has sync enabled'.format(user.id))
+```
+
+[object_get]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.get
+[user_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User
+
+Get the Current User's Information
+----------------------------------
+
+To get the current user, call [`client.user(user_id='me')`][user_init] to create the [`User`][user_class] object and
+then call [`user.get(fields=None)`][object_get] to retrieve the user information from the API.
+
+```python
+current_user = client.user().get()
+```
+
+[user_init]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.user
+
+Create An Enterprise User
+-------------------------
+
+To create an enterprise user, call the [`client.create_user(name, login, **user_attributes)`][create_user] method.
+This method returns a new [`User`][user_class] object.
+
+```python
+new_user = client.create_user('Dummy User', 'user@example.com')
+```
+
+[create_user]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.create_user
+
+Create An App User
+------------------
+
+Custom applications may create App Users, which represent a "headless" user managed exclusively by the application.
+These users can only be accessed via the API, and cannot login to the web application or other Box services.
+
+To create a new app user, call [`client.create_user(name, login=None, **user_attributes)`][create_user] without a
+`login` value.  This returns the [`User`][user_class] object for the new app user.
+
+```python
+new_app_user = client.create_user('App User 123', login=None)
+```
+
+Update User
+-----------
+
+To update a user object, call the [`user.update_info(data)`][update_info] method with a `dict` of fields to update
+on the user.
+
+```python
+user_id = '33333'
+user = client.user(user_id)
+updated_user = user.update_info({'name': 'Smart User'})
+```
+
+[update_info]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.base_object.BaseObject.update_info
+
+Delete User
+-----------
+
+To delete a user call the [`user.delete(notify=True, force=False)`][delete] method.  The method returns `True` to
+indicate that the deletion succeeded.
+
+The `notify` parameter determines whether the user should receive an email about the deletion,
+and the `force` parameter will cause the user to be deleted even if they still have files in their account.  If `force`
+is set to `False` and the user still has files in their account, the deletion will fail.
+
+```python
+user_id = '33333'
+client.user(user_id).delete(force=True)
+```
+
+[delete]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User.delete
+
+Invite User to Enterprise
+-------------------------
+
+To invite an existing user to join an Enterprise call the [`enterprise.invite_user(user_email)`][invite_user] method.  This
+method returns an [`Invite`][invite_class] object representing the status of the invitation.
+
+```python
+enterprise = client.get_current_enterprise()
+invitation = enterprise.invite_user('user@example.com')
+```
+
+[invite_user]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.enterprise.Enterprise.invite_user
+[invite_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.invite.Invite
+
+Get Email Aliases
+-----------------
+
+To get a user's email aliases call the [`user.get_email_aliases(limit=None, fields=None)`][get_email_aliases] method.
+This method returns a [`BoxObjectCollection`][box_object_collection] used to iterate over the collection of
+[`EmailAlias`][email_alias_class] objects.
+
+```python
+user_id = '33333'
+user = client.user(user_id)
+email_aliases = user.get_email_aliases()
+for alias in email_aliases:
+    print('User {0} has email alias {1}'.format(user.id, alias.email))
+```
+
+[get_email_aliases]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User.get_email_aliases
+[box_object_collection]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.pagination.box_object_collection.BoxObjectCollection
+[email_alias_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.email_alias.EmailAlias
+
+Add Email Alias
+---------------
+
+To add an email alias for a user, call the [`user.add_email_alias(email)`][add_email_alias] method with the email
+address to add as an email alias for the user.  This will allow the user to log in and be collaborated by this email
+in addition to their login email address.  The method returns an [`EmailAlias`][email_alias_class] object.
+
+```python
+user_id = '33333'
+user = client.user(user_id)
+email_alias = user.add_email_alias('alias@example.com')
+```
+
+[add_email_alias]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User.add_email_alias
+
+Remove Email Alias
+------------------
+
+To remove an email alias from a user, call the [`user.remove_email_alias(email_alias)`][remove_email_alias] method with
+the [`EmailAlias`][email_alias_class] object to remove.  The method returns `True` to signify that the removal succeeded.
+
+```python
+user_id = '33333'
+email_alias_id = '12345'
+
+user = client.user(user_id)
+email_alias = client.email_alias(email_alias_id)
+
+user.remove_email_alias(email_alias)
+```
+
+[remove_email_alias]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User.remove_email_alias
+
+Get Enterprise Users
+--------------------
+
+To get the users in an enterprise, call
+[`client.users(limit=None, offset=0, filter_term=None, user_type=None, fields=None)`][get_users].  You can specify
+a `filter_term` to filter on the user's `name` and `login` fields, or select a `user_type` to filter down to only
+managed or external users.  This method returns a [`BoxObjectCollection`][box_object_collection] used to iterate over
+the collection of [`User`][user_class] objects.
+
+```python
+users = client.users(user_type='all')
+for user in users:
+    print('{0} (User ID: {1})'.format(user.name, user.id))
+```
+
+[get_users]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.client.html#boxsdk.client.client.Client.users
+
+Transfer User Content
+---------------------
+
+To move all of a user's content to a different user, call the
+[`user.transfer_content(self, destination_user, notify=None, fields=None)`][transfer_content] method with the
+[`User`][user_class] object representing the destination user.  This will create a new folder in the destination user's
+account, containing all files and folders from the original user's account; the method returns a
+[`Folder`][folder_class] object representing this new folder in the destination user's account.
+
+```python
+source_user_id = '33333'
+destination_user_id = '44444'
+
+user = client.user(source_user_id)
+destination_user = client.user(destination_user_id)
+
+folder = user.transfer_content(destination_user)
+print('Created new folder "{0}" in the account of user {1}'.format(folder.name, destination_user.id))
+```
+
+[transfer_content]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.user.User.transfer_content
+[folder_class]: https://box-python-sdk.readthedocs.io/en/latest/boxsdk.object.html#boxsdk.object.folder.Folder


Test patch:
diff --git a/test/unit/client/test_client.py b/test/unit/client/test_client.py
index b8ad681..89452b2 100644
--- a/test/unit/client/test_client.py
+++ b/test/unit/client/test_client.py
@@ -22,6 +22,7 @@
 from boxsdk.network.default_network import DefaultNetworkResponse
 from boxsdk.object.collaboration import Collaboration
 from boxsdk.object.collaboration_whitelist import CollaborationWhitelist
+from boxsdk.object.email_alias import EmailAlias
 from boxsdk.object.collection import Collection
 from boxsdk.object.comment import Comment
 from boxsdk.object.device_pinner import DevicePinner
@@ -30,6 +31,7 @@
 from boxsdk.object.folder import Folder
 from boxsdk.object.file import File
 from boxsdk.object.group import Group
+from boxsdk.object.invite import Invite
 from boxsdk.object.storage_policy import StoragePolicy
 from boxsdk.object.storage_policy_assignment import StoragePolicyAssignment
 from boxsdk.object.terms_of_service import TermsOfService
@@ -279,6 +281,17 @@ def create_user_response():
     return mock_network_response
 
 
+@pytest.fixture(scope='module')
+def create_invite_response():
+    # pylint:disable=redefined-outer-name
+    mock_network_response = Mock(DefaultNetworkResponse)
+    mock_network_response.json.return_value = {
+        'type': 'invite',
+        'id': 1234,
+    }
+    return mock_network_response
+
+
 @pytest.fixture(params=('file', 'folder'))
 def test_item_and_response(mock_file, test_folder, mock_file_response, mock_folder_response, request):
     if request.param == 'file':
@@ -374,8 +387,11 @@ def device_pins_response(device_pin_id_1, device_pin_id_2):
 
 
 @pytest.mark.parametrize('test_class, factory_method_name', [
+    (EmailAlias, 'email_alias'),
+    (Enterprise, 'enterprise'),
     (Folder, 'folder'),
     (File, 'file'),
+    (Invite, 'invite'),
     (User, 'user'),
     (Group, 'group'),
     (GroupMembership, 'group_membership'),
@@ -1070,11 +1086,38 @@ def test_device_pins_for_enterprise(mock_client, mock_box_session, device_pins_r
     pins = mock_client.device_pinners(enterprise_id, direction='asc')
     for pin, expected_id in zip(pins, [device_pin_id_1, device_pin_id_2]):
         assert pin.object_id == expected_id
-        # pylint:disable=protected-access
-        assert pin._session == mock_box_session
+        assert pin._session == mock_box_session  # pylint:disable=protected-access
     mock_box_session.get.assert_called_once_with(expected_url, params={'direction': 'asc'})
 
 
+def test_get_current_enterprise(mock_client, mock_box_session):
+    expected_url = '{0}/users/me'.format(API.BASE_API_URL)
+    expected_params = {
+        'fields': 'enterprise'
+    }
+    enterprise_id = '44444'
+    enterprise_name = 'Acme, Inc.'
+    user_json = {
+        'type': 'user',
+        'id': '33333',
+        'enterprise': {
+            'type': 'enterprise',
+            'id': enterprise_id,
+            'name': enterprise_name,
+        },
+    }
+    mock_box_session.get.return_value.json.return_value = user_json
+
+    enterprise = mock_client.get_current_enterprise()
+
+    mock_box_session.get.assert_called_once_with(expected_url, params=expected_params, headers=None)
+    assert isinstance(enterprise, Enterprise)
+    assert enterprise.object_id == enterprise_id
+    assert enterprise._session == mock_box_session  # pylint:disable=protected-access
+    assert enterprise.name == enterprise_name
+    mock_box_session.get.assert_called_once_with(expected_url, headers=None, params={'fields': 'enterprise'})
+
+
 def test_comment(mock_client):
     # pylint:disable=redefined-outer-name
     comment_id = '12345'
diff --git a/test/unit/object/test_enterprise.py b/test/unit/object/test_enterprise.py
new file mode 100644
index 0000000..3e9e025
--- /dev/null
+++ b/test/unit/object/test_enterprise.py
@@ -0,0 +1,44 @@
+# coding: utf-8
+
+from __future__ import absolute_import, unicode_literals
+
+import json
+import pytest
+
+from boxsdk.config import API
+from boxsdk.object.enterprise import Enterprise
+from boxsdk.object.invite import Invite
+
+
+@pytest.fixture()
+def test_enterprise(mock_box_session):
+    return Enterprise(
+        session=mock_box_session,
+        object_id='test_enterprise_id',
+    )
+
+
+def test_invite_user(test_enterprise, mock_box_session):
+    # pylint:disable=redefined-outer-name
+    expected_url = '{0}/invites'.format(API.BASE_API_URL)
+    test_user_login = 'test@user.com'
+    expected_body = json.dumps({
+        'enterprise': {
+            'id': test_enterprise.object_id,
+        },
+        'actionable_by': {
+            'login': test_user_login,
+        },
+    })
+    invite_json = {
+        'type': 'invite',
+        'id': '11111',
+        'status': 'pending',
+    }
+    mock_box_session.post.return_value.json.return_value = invite_json
+    new_invite = test_enterprise.invite_user(test_user_login)
+    mock_box_session.post.assert_called_once_with(expected_url, data=expected_body)
+    assert isinstance(new_invite, Invite)
+    assert new_invite.object_id == invite_json['id']
+    assert new_invite._session == mock_box_session  # pylint: disable=protected-access
+    assert new_invite.status == invite_json['status']
diff --git a/test/unit/object/test_user.py b/test/unit/object/test_user.py
index 796c096..41ad7c3 100644
--- a/test/unit/object/test_user.py
+++ b/test/unit/object/test_user.py
@@ -1,10 +1,14 @@
 # coding: utf-8
 
 from __future__ import unicode_literals
+
+import json
 import pytest
 
 from mock import Mock
 from boxsdk.config import API
+from boxsdk.object.email_alias import EmailAlias
+from boxsdk.object.folder import Folder
 from boxsdk.object.storage_policy_assignment import StoragePolicyAssignment
 from boxsdk.network.default_network import DefaultNetworkResponse
 
@@ -56,6 +60,132 @@ def memberships_response():
     return mock_network_response
 
 
+@pytest.fixture()
+def test_email_alias(mock_box_session):
+    return EmailAlias(
+        session=mock_box_session,
+        object_id='test_alias_id',
+    )
+
+
+def test_update(mock_user, mock_box_session):
+    # pylint:disable=redefined-outer-name, protected-access
+    user_id = mock_user.object_id
+    expected_url = mock_box_session.get_url('users', user_id)
+    user = {
+        'type': 'user',
+        'name': 'Test User',
+        'id': 1234,
+    }
+    data = {
+        'name': 'New User',
+    }
+    mock_box_session.put.return_value.json.return_value = user
+    new_user = mock_user.update_info(data)
+    mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), headers=None, params=None)
+    assert new_user.id == user['id']
+    assert new_user.type == user['type']
+    assert new_user.name == user['name']
+
+
+def test_delete(mock_user, mock_box_session):
+    user_id = mock_user.object_id
+    expected_url = mock_box_session.get_url('users', user_id)
+    expected_params = {
+        'notify': True,
+        'force': False,
+    }
+    mock_user.delete()
+    mock_box_session.delete.assert_called_once_with(
+        expected_url,
+        expect_json_response=False,
+        params=expected_params,
+        headers=None,
+    )
+
+
+def test_get_email_aliases(mock_user, mock_box_session):
+    # pylint:disable=redefined-outer-name
+    alias1_json = {
+        'type': 'email_alias',
+        'id': '12345',
+        'email': 'foo@example.com',
+    }
+    alias2_json = {
+        'type': 'email_alias',
+        'id': '67890',
+        'email': 'bar@example.com',
+    }
+    mock_box_session.get.return_value.json.return_value = {
+        'total_count': 2,
+        'entries': [alias1_json, alias2_json],
+    }
+    aliases = mock_user.get_email_aliases()
+    for alias, alias_json in zip(aliases, [alias1_json, alias2_json]):
+        assert alias.object_id == alias_json['id']
+        # pylint:disable=protected-access
+        assert alias._session == mock_box_session
+        assert alias.email == alias_json['email']
+
+
+def test_add_email_alias_returns_the_correct_email_alias_object(mock_user, mock_box_session):
+    # pylint:disable=redefined-outer-name
+    test_email_alias = 'test@example.com'
+    expected_url = '{0}/users/{1}/email_aliases'.format(API.BASE_API_URL, mock_user.object_id)
+    expected_body = json.dumps({
+        'email': test_email_alias,
+    })
+    mock_box_session.post.return_value.json.return_value = {
+        'type': 'email_alias',
+        'id': '1234',
+        'email': test_email_alias,
+    }
+    new_email_alias = mock_user.add_email_alias(test_email_alias)
+    mock_box_session.post.assert_called_once_with(expected_url, data=expected_body)
+    assert isinstance(new_email_alias, EmailAlias)
+    assert new_email_alias._session == mock_box_session  # pylint: disable=protected-access
+    assert new_email_alias.object_id == '1234'
+
+
+def test_remove_email_alias(mock_user, mock_box_session, test_email_alias):
+    expected_url = '{0}/users/{1}/email_aliases/{2}'.format(API.BASE_API_URL, mock_user.object_id, test_email_alias.object_id)
+    mock_box_session.delete.return_value.ok = True
+
+    result = mock_user.remove_email_alias(test_email_alias)
+
+    mock_box_session.delete.assert_called_once_with(expected_url, expect_json_response=False)
+    assert result is True
+
+
+@pytest.mark.parametrize('notify,fields,expected_params', [
+    (None, None, {}),
+    (True, None, {'notify': True}),
+    (False, None, {'notify': False}),
+    (None, ['type', 'id', 'name'], {'fields': 'type,id,name'}),
+    (False, ['type', 'id'], {'notify': False, 'fields': 'type,id'}),
+])
+def test_transfer_content(mock_user, mock_box_session, notify, fields, expected_params):
+    # pylint:disable=redefined-outer-name
+    expected_url = "{0}/users/{1}/folders/0".format(API.BASE_API_URL, mock_user.object_id)
+    expected_body = json.dumps({
+        'owned_by': {
+            'id': mock_user.object_id
+        },
+    })
+    move_items_response = {
+        'type': 'folder',
+        'id': '12345',
+        'name': 'That Other User\'s Content',
+    }
+    mock_box_session.put.return_value.json.return_value = move_items_response
+    moved_item = mock_user.transfer_content(mock_user, notify=notify, fields=fields)
+    mock_box_session.put.assert_called_once_with(expected_url, data=expected_body, params=expected_params)
+    assert isinstance(moved_item, Folder)
+    assert moved_item.id == move_items_response['id']
+    assert moved_item.name == move_items_response['name']
+    assert moved_item._session == mock_box_session  # pylint:disable=protected-access
+
+
 def test_get_group_memberships(
         mock_user,
         mock_box_session,