Repository: box/box-python-sdk
Pull request #331 (2018-08-21 22:24:26)

Legal holds v2


Patch:
diff --git a/boxsdk/client/client.py b/boxsdk/client/client.py
index b3abb81..94988b4 100644
--- a/boxsdk/client/client.py
+++ b/boxsdk/client/client.py
@@ -159,6 +159,144 @@ def collaboration(self, collab_id):
         """
         return self.translator.translate('collaboration')(session=self._session, object_id=collab_id)
 
+    def legal_hold_policy(self, policy_id):
+        """
+        Initialize a :class:`LegalHoldPolicy` object, whose box id is policy_id.
+
+        :param policy_id:
+            The box ID of the :class:`LegalHoldPolicy` object.
+        :type policy_id:
+            `unicode`
+        :return:
+            A :class:`LegalHoldPolicy` object with the given entry ID.
+        :rtype:
+            :class:`LegalHoldPolicy`
+        """
+        return self.translator.translate('legal_hold_policy')(session=self._session, object_id=policy_id)
+
+    def legal_hold_policy_assignment(self, policy_assignment_id):
+        """
+        Initialize a :class:`LegalHoldPolicyAssignment` object, whose box id is policy_assignment_id.
+
+        :param policy_assignment_id:
+            The assignment ID of the :class:`LegalHoldPolicyAssignment` object.
+        :type policy_assignment_id:
+            `unicode`
+        :return:
+            A :class:`LegalHoldPolicyAssignment` object with the given entry ID.
+        :rtype:
+            :class:`LegalHoldPolicyAssignment`
+        """
+        return self.translator.translate('legal_hold_policy_assignment')(session=self._session, object_id=policy_assignment_id)
+
+    def legal_hold(self, hold_id):
+        """
+        Initialize a :class:`LegalHold` object, whose box id is policy_id.
+
+        :param hold_id:
+            The legal hold ID of the :class:`LegalHold` object.
+        :type hold_id:
+            `unicode`
+        :return:
+            A :class:`LegalHold` object with the given entry ID.
+        :rtype:
+            :class:`LegalHold`
+        """
+        return self.translator.translate('legal_hold')(session=self._session, object_id=hold_id)
+
+    def create_legal_hold_policy(
+            self,
+            policy_name,
+            description=None,
+            filter_starting_at=None,
+            filter_ending_at=None,
+            is_ongoing=None
+    ):
+        """
+        Create a legal hold policy.
+
+        :param policy_name:
+            The legal hold policy's display name.
+        :type policy_name:
+            `unicode`
+        :param description:
+            The description of the legal hold policy.
+        :type description:
+            `unicode` or None
+        :param filter_starting_at:
+            The start time filter for legal hold policy
+        :type filter_starting_at:
+            `unicode` or None
+        :param filter_ending_at:
+            The end time filter for legal hold policy
+        :type filter_ending_at:
+            `unicode` or None
+        :param is_ongoing:
+            After initialization, Assignments under this Policy will continue applying to
+            files based on events, indefinitely.
+        :type is_ongoing:
+            `bool` or None
+        :returns:
+            A legal hold policy object
+        :rtype:
+            :class:`LegalHoldPolicy
+        """
+        url = self.get_url('legal_hold_policies')
+        policy_attributes = {'policy_name': policy_name}
+        if description is not None:
+            policy_attributes['description'] = description
+        if filter_starting_at is not None:
+            policy_attributes['filter_starting_at'] = filter_starting_at
+        if filter_ending_at is not None:
+            policy_attributes['filter_ending_at'] = filter_ending_at
+        if is_ongoing is not None:
+            policy_attributes['is_ongoing'] = is_ongoing
+        box_response = self._session.post(url, data=json.dumps(policy_attributes))
+        response = box_response.json()
+        return self.translator.translate(response['type'])(
+            session=self._session,
+            object_id=response['id'],
+            response_object=response,
+        )
+
+    def get_legal_hold_policies(self, policy_name=None, limit=None, marker=None, fields=None):
+        """
+        Get the entries in the legal hold policy using limit-offset paging.
+
+        :param policy_name:
+            The name of the legal hold policy case insensitive to search for
+        :type policy_name:
+            `unicode` or None
+        :param limit:
+            The maximum number of entries to return per page. If not specified, then will use the server-side default.
+        :type limit:
+            `int` or None
+        :param marker:
+            The paging marker to start paging from.
+        :type marker:
+            `unicode` or None
+        :param fields:
+            List of fields to request.
+        :type fields:
+            `Iterable` of `unicode`
+        :returns:
+            An iterator of the entries in the legal hold policy
+        :rtype:
+            :class:`BoxObjectCollection`
+        """
+        additional_params = {}
+        if policy_name is not None:
+            additional_params['policy_name'] = policy_name
+        return MarkerBasedObjectCollection(
+            session=self._session,
+            url=self.get_url('legal_hold_policies'),
+            additional_params=additional_params,
+            limit=limit,
+            marker=marker,
+            fields=fields,
+            return_full_pages=False,
+        )
+
     def collection(self, collection_id):
         """
         Initialize a :class:`Collection` object, whose box ID is collection_id.
diff --git a/boxsdk/object/__init__.py b/boxsdk/object/__init__.py
index 697bb0d..1e213c6 100644
--- a/boxsdk/object/__init__.py
+++ b/boxsdk/object/__init__.py
@@ -16,6 +16,9 @@
     'folder',
     'group',
     'group_membership',
+    'legal_hold',
+    'legal_hold_policy',
+    'legal_hold_policy_assignment',
     'recent_item',
     'search',
     'user',
diff --git a/boxsdk/object/base_api_json_object.py b/boxsdk/object/base_api_json_object.py
index eecbdd4..44bc019 100644
--- a/boxsdk/object/base_api_json_object.py
+++ b/boxsdk/object/base_api_json_object.py
@@ -128,7 +128,6 @@ def _description(self):
     @property
     def object_type(self):
         """Return the Box type for the object.
-
         :rtype:
             `unicode`
         """
diff --git a/boxsdk/object/legal_hold.py b/boxsdk/object/legal_hold.py
new file mode 100644
index 0000000..2cd10d0
--- /dev/null
+++ b/boxsdk/object/legal_hold.py
@@ -0,0 +1,12 @@
+# coding:utf-8
+from __future__ import unicode_literals, absolute_import
+from .base_object import BaseObject
+
+
+class LegalHold(BaseObject):
+
+    """Represents the legal hold policy for a file version"""
+    _item_type = 'legal_hold'
+
+    def get_url(self, *args):
+        return self._session.get_url('file_version_legal_holds', self._object_id, *args)
diff --git a/boxsdk/object/legal_hold_policy.py b/boxsdk/object/legal_hold_policy.py
new file mode 100644
index 0000000..9257bc5
--- /dev/null
+++ b/boxsdk/object/legal_hold_policy.py
@@ -0,0 +1,123 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+import json
+
+from .base_object import BaseObject
+from ..pagination.marker_based_object_collection import MarkerBasedObjectCollection
+
+
+class LegalHoldPolicy(BaseObject):
+    """Represents a Box legal_hold_policy"""
+
+    _item_type = 'legal_hold_policy'
+
+    def get_url(self, *args):
+        return self._session.get_url('legal_hold_policies', self._object_id, *args)
+
+    def assign(self, assignee):
+        """Assign legal hold policy
+
+        :param assignee:
+            The `file_version`, `file`, `folder`, or `user` to assign the legal hold policy to.
+        :type assignee:
+            :class:`FileVersion` :class:`File` or :class:`Folder` or :class:`User`
+        :returns:
+            A legal hold policy assignment object
+        :rtype:
+            :class:`LegalHoldPolicyAssignment`
+        """
+        url = self._session.get_url('legal_hold_policy_assignments')
+        body = {
+            'policy_id': self.object_id,
+            'assign_to': {
+                'type': assignee.object_type,
+                'id': assignee.object_id
+            }
+        }
+        response = self._session.post(url, data=json.dumps(body)).json()
+        return self.translator.translate(response['type'])(
+            self._session,
+            response['id'],
+            response,
+        )
+
+    def get_assignments(self, assign_to_type=None, assign_to_id=None, limit=None, marker=None, fields=None):
+        """
+        Get the entries in the legal hold policy assignment using limit-offset paging.
+
+        :param assign_to_type:
+            Filter assignments of this type only. Can be `file_version`, `file`, `folder`, or `user`
+        :type assign_to_type:
+            `unicode` or None
+        :param assign_to_id:
+            Filter assignments to this ID only
+        :type assign_to_id:
+            `unicode` or None
+        :param limit:
+            The maximum number of entries to return per page. If not specified, then will use the server-side default.
+        :type limit:
+            `int` or None
+        :param marker:
+            The paging marker to start paging from
+        :type marker:
+            `unicode` or None
+        :param fields:
+            List of fields to request
+        :type fields:
+            `Iterable` of `unicode`
+        :returns:
+            An iterator of the entries in the legal hold policy assignment
+        :rtype:
+            :class:`BoxObjectCollection`
+        """
+        additional_params = {
+            'policy_id': self.object_id,
+        }
+        if assign_to_type is not None:
+            additional_params['assign_to_type'] = assign_to_type
+        if assign_to_id is not None:
+            additional_params['assign_to_id'] = assign_to_id
+        return MarkerBasedObjectCollection(
+            session=self._session,
+            url=self._session.get_url('legal_hold_policy_assignments'),
+            additional_params=additional_params,
+            limit=limit,
+            marker=marker,
+            fields=fields,
+            return_full_pages=False,
+        )
+
+    def get_file_version_legal_holds(self, limit=None, marker=None, fields=None):
+        """
+        Get legal holds for a file version.
+
+        :param limit:
+            The maximum number of entries to return per page. If not specified, then will use the server-side default.
+        :type limit:
+            `int` or None
+        :param marker:
+            The paging marker to start paging from
+        :type marker:
+            `unicode` or None
+        :param fields:
+            List of fields to request
+        :type fields:
+            `Iterable` of `unicode`
+        :returns:
+            An iterator of the entries in the file version legal holds
+        :rtype:
+            :class:`BoxObjectCollection`
+        """
+        additional_params = {
+            'policy_id': self.object_id,
+        }
+        return MarkerBasedObjectCollection(
+            session=self._session,
+            url=self._session.get_url('file_version_legal_holds'),
+            additional_params=additional_params,
+            limit=limit,
+            marker=marker,
+            fields=fields,
+            return_full_pages=False,
+        )
diff --git a/boxsdk/object/legal_hold_policy_assignment.py b/boxsdk/object/legal_hold_policy_assignment.py
new file mode 100644
index 0000000..5c5ed8e
--- /dev/null
+++ b/boxsdk/object/legal_hold_policy_assignment.py
@@ -0,0 +1,9 @@
+# coding:utf-8
+from __future__ import unicode_literals, absolute_import
+from .base_object import BaseObject
+
+
+class LegalHoldPolicyAssignment(BaseObject):
+
+    """Represents the legal hold policy assignment"""
+    _item_type = 'legal_hold_policy_assignment'
diff --git a/docs/legal_hold.md b/docs/legal_hold.md
new file mode 100644
index 0000000..c6d76ed
--- /dev/null
+++ b/docs/legal_hold.md
@@ -0,0 +1,139 @@
+Legal Holds Policy
+==================
+
+Legal Hold Policy information describes the basic characteristics of the Policy,
+such as name, description, and filter dates. It is important to note that the legal hold object contains no information about what this policy applies to.
+
+If an order of discovery is received or the customer is part of an ongoing litigation, a legal hold policy can be created to keep track of everything that needs to be held. The actual holding is done via the Legal Hold Assignments endpoint.
+
+
+Get Legal Hold Policy Information
+---------------------------------
+
+To retrieve information about a legal hold policy, use `legal_hold_policy.get()`.
+
+```python
+policy_id = '1234'
+policy_info = client.legal_hold_policy(policy_id).get()
+```
+
+List Legal Hold Policies
+------------------------
+
+To retrieve an iterable of legal hold policies in the enterprise, you can call `client.get_legal_hold_policies(policy_name=None, limit=None, marker=None, fields=None)`.
+
+```python
+policies = client.get_legal_hold_policies()
+for policy in policies:
+    # Do something
+```
+
+You can also specify a name filter for the policies you wish to retrieve by using
+
+```python
+policies = clinet.legal_hold_policies(policy_name='Test Policy')
+for policy in policies:
+    # Do something
+```
+
+Create New Legal Hold Policy
+----------------------------
+
+To create a new legal hold policy, use `client.create_legal_hold_policy(policy_name, description=None, filter_starting_at=None, filter_ending_at=None, is_ongoing=None)`.
+
+```python
+new_policy = client.create_legal_hold_policy('New Policy', is_ongoing=True)
+```
+
+Update Legal Hold Policy
+------------------------
+
+To update an existing legal hold policy, use `legal_hold_policy.update_info()`
+
+```python
+policy_id = '1234'
+policy_update = {'description': 'New Description', 'release_notes': 'Example Notes'}
+updated_policy = client.legal_hold_policy(policy_id).update_info(policy_update)
+```
+
+Delete Legal Hold Policy
+------------------------
+
+To delete a legal hold policy, use `legal_hold_policy.delete()`
+
+```python
+policy_id = '1234'
+client.legal_hold_policy(policy_id).delete()
+```
+
+Assign Legal Hold Policy
+------------------------
+
+To assign a legal hold policy, use `legal_hold_policy.assign(assignee)`. You can assign a legal hold policy to a `file_version`, `file`, `user`, or `folder`.
+
+```python
+folder_id = '1234'
+policy_id = '5678'
+folder_to_assign = client.folder(folder_id)
+assignment = client.legal_hold_policy(policy_id).assign(folder_to_assign)
+```
+
+List Legal Hold Policy Assignments
+----------------------------------
+
+To retrieve an iterable of legal hold policy assignments, use `legal_hold_policy.get_assignments(assign_to_type=None, assign_to_id=None, limit=None, marker=None, fields=None)`
+
+```python
+assignments = client.legal_hold_policy('1234').get_assignments()
+for assignment in assignments:
+    # Do something
+```
+
+To filter by the assignee `type` and `id` you can use pass in the `assign_to_type` and `assign_to_id` filter.
+
+```python
+folder_id = '1111'
+assignments = client.legal_hold_policy('1234').get_assignments('folder', folder_id)
+for assignment in assignments:
+    # Do something
+```
+
+Get Legal Hold Assignment Information
+-------------------------------------
+
+To retrieve information about the legal hold policy assignment, use `legal_hold_policy_assignment.get()`
+
+```python
+assignment_id = '1111'
+assignment_info = client.legal_hold_policy_assignment(assignment_id).get()
+```
+
+Delete Legal Hold Assignment
+----------------------------
+
+To delete an existing legal hold policy assignment, use `legal_hold_policy_assignment.delete()`
+
+```python
+assignment_id = '1111'
+client.legal_hold_policy_assignment.delete()
+```
+
+List File Version Legal Holds
+-----------------------------
+
+To return an iterable of all file version legal holds, use `legal_hold_policy.get_file_version_legal_holds()`
+
+```python
+legal_holds = client.legal_hold_policy('1234').get_file_version_legal_holds()
+for legal_hold in legal_holds:
+    # Do something
+```
+
+Get Information about a File Version Legal Hold
+-----------------------------------------------
+
+To retrieve information about a file version legal hold, use `legal_hold.get()`
+
+```python
+legal_hold = client.legal_hold('1234').get()
+```


Test patch:
diff --git a/test/unit/client/test_client.py b/test/unit/client/test_client.py
index 90b0274..9fc9392 100644
--- a/test/unit/client/test_client.py
+++ b/test/unit/client/test_client.py
@@ -24,6 +24,7 @@
 from boxsdk.object.group import Group
 from boxsdk.object.user import User
 from boxsdk.object.group_membership import GroupMembership
+from boxsdk.object.legal_hold_policy import LegalHoldPolicy
 from boxsdk.pagination.marker_based_object_collection import MarkerBasedObjectCollection
 
 
@@ -117,6 +118,42 @@ def groups_response(group_id_1, group_id_2):
     return mock_network_response
 
 
+@pytest.fixture(scope='module')
+def legal_hold_policy_id_1():
+    return 101
+
+
+@pytest.fixture(scope='module')
+def legal_hold_policy_id_2():
+    return 202
+
+
+@pytest.fixture(scope='module')
+def legal_hold_policies_response(legal_hold_policy_id_1, legal_hold_policy_id_2):
+    # pylint:disable=redefined-outer-name
+    mock_network_response = Mock(DefaultNetworkResponse)
+    mock_network_response.json.return_value = {
+        'entries': [
+            {'type': 'legal_hold_policy', 'id': legal_hold_policy_id_1, 'name': 'Test Policy 1'},
+            {'type': 'legal_hold_policy', 'id': legal_hold_policy_id_2, 'name': 'Test Policy 2'},
+        ],
+        'limit': 5,
+    }
+    return mock_network_response
+
+
+@pytest.fixture(scope='module')
+def create_policy_response():
+    # pylint:disable=redefined-outer-name
+    mock_network_response = Mock(DefaultNetworkResponse)
+    mock_network_response.json.return_value = {
+        'type': 'legal_hold_policy',
+        'id': 1234,
+        'policy_name': 'Test Policy'
+    }
+    return mock_network_response
+
+
 @pytest.fixture(scope='module')
 def create_group_response():
     # pylint:disable=redefined-outer-name
@@ -323,6 +360,38 @@ def test_create_group_returns_the_correct_group_object(mock_client, mock_box_ses
     assert new_group.name == test_group_name
 
 
+def test_create_legal_hold_policy_returns_the_correct_policy_object(mock_client, mock_box_session, create_policy_response):
+    # pylint:disable=redefined-outer-name
+    test_policy_name = 'Test Policy'
+    expected_body = {
+        'policy_name': test_policy_name
+    }
+    value = json.dumps(expected_body)
+    mock_box_session.post.return_value = create_policy_response
+    new_policy = mock_client.create_legal_hold_policy(test_policy_name)
+    assert len(mock_box_session.post.call_args_list) == 1
+    assert mock_box_session.post.call_args[0] == ("{0}/legal_hold_policies".format(API.BASE_API_URL),)
+    assert mock_box_session.post.call_args[1] == {'data': value}
+    assert isinstance(new_policy, LegalHoldPolicy)
+    assert new_policy.policy_name == test_policy_name
+
+
+def test_legal_hold_policies_return_the_correct_policy_objects(
+        mock_client,
+        mock_box_session,
+        legal_hold_policies_response,
+        legal_hold_policy_id_1,
+        legal_hold_policy_id_2,
+):
+    # pylint:disable=redefined-outer-name
+    mock_box_session.get.return_value = legal_hold_policies_response
+    policies = mock_client.get_legal_hold_policies()
+    for policy, expected_id in zip(policies, [legal_hold_policy_id_1, legal_hold_policy_id_2]):
+        assert policy.object_id == expected_id
+        # pylint:disable=protected-access
+        assert policy._session == mock_box_session
+
+
 def test_get_recent_items_returns_the_correct_items(mock_client, mock_box_session, recent_items_response, file_id):
     mock_box_session.get.return_value = recent_items_response
     recent_items = mock_client.get_recent_items()
diff --git a/test/unit/object/conftest.py b/test/unit/object/conftest.py
index cd371c3..cabf113 100644
--- a/test/unit/object/conftest.py
+++ b/test/unit/object/conftest.py
@@ -11,8 +11,11 @@
 from boxsdk.object.comment import Comment
 from boxsdk.object.device_pinner import DevicePinner
 from boxsdk.object.file import File
+from boxsdk.object.legal_hold import LegalHold
 from boxsdk.object.folder import Folder
 from boxsdk.object.group import Group
+from boxsdk.object.legal_hold_policy import LegalHoldPolicy
+from boxsdk.object.legal_hold_policy_assignment import LegalHoldPolicyAssignment
 from boxsdk.object.user import User
 from boxsdk.object.search import Search
 from boxsdk.object.web_link import WebLink
@@ -93,6 +96,21 @@ def test_group(mock_box_session, mock_group_id):
     return Group(mock_box_session, mock_group_id)
 
 
+@pytest.fixture()
+def test_legal_hold_policy(mock_box_session, mock_object_id):
+    return LegalHoldPolicy(mock_box_session, mock_object_id)
+
+
+@pytest.fixture()
+def test_legal_hold_policy_assignment(mock_box_session, mock_object_id):
+    return LegalHoldPolicyAssignment(mock_box_session, mock_object_id)
+
+
+@pytest.fixture()
+def test_legal_hold(mock_box_session, mock_object_id):
+    return LegalHold(mock_box_session, mock_object_id)
+
+
 @pytest.fixture()
 def test_search(mock_box_session):
     return Search(mock_box_session)
diff --git a/test/unit/object/test_legal_hold.py b/test/unit/object/test_legal_hold.py
new file mode 100644
index 0000000..33f9835
--- /dev/null
+++ b/test/unit/object/test_legal_hold.py
@@ -0,0 +1,24 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+from boxsdk.object.legal_hold import LegalHold
+from boxsdk.config import API
+
+
+def test_get(test_legal_hold, mock_box_session):
+    file_version_id = '1234'
+    expected_url = '{0}/file_version_legal_holds/{1}'.format(API.BASE_API_URL, test_legal_hold.object_id)
+    mock_box_session.get.return_value.json.return_value = {
+        'type': 'legal_hold',
+        'id': test_legal_hold.object_id,
+        'file_version': {
+            'type': 'file_version',
+            'id': file_version_id,
+        },
+    }
+    file_version_legal_hold = test_legal_hold.get()
+    mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None)
+    assert isinstance(file_version_legal_hold, LegalHold)
+    assert file_version_legal_hold['file_version']['id'] == file_version_id
+    assert file_version_legal_hold.type == 'legal_hold'
+    assert file_version_legal_hold.id == test_legal_hold.object_id
diff --git a/test/unit/object/test_legal_hold_assignment.py b/test/unit/object/test_legal_hold_assignment.py
new file mode 100644
index 0000000..f5db72f
--- /dev/null
+++ b/test/unit/object/test_legal_hold_assignment.py
@@ -0,0 +1,51 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+import pytest
+
+from mock import Mock
+from boxsdk.object.legal_hold_policy_assignment import LegalHoldPolicyAssignment
+from boxsdk.config import API
+from boxsdk.network.default_network import DefaultNetworkResponse
+
+
+def test_get(test_legal_hold_policy_assignment, mock_box_session):
+    expected_url = '{0}/legal_hold_policy_assignments/{1}'.format(API.BASE_API_URL, test_legal_hold_policy_assignment.object_id)
+    mock_box_session.get.return_value.json.return_value = {
+        'type': 'legal_hold_policy_assignment',
+        'id': test_legal_hold_policy_assignment.object_id,
+        'assigned_to': {
+            'type': 'user',
+            'id': '1234',
+        },
+    }
+    legal_hold_policy_assignment = test_legal_hold_policy_assignment.get()
+    mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None)
+    assert isinstance(legal_hold_policy_assignment, LegalHoldPolicyAssignment)
+    assert legal_hold_policy_assignment.type == 'legal_hold_policy_assignment'
+    assert legal_hold_policy_assignment.object_id == test_legal_hold_policy_assignment.object_id
+    assert legal_hold_policy_assignment['assigned_to']['type'] == 'user'
+    assert legal_hold_policy_assignment['assigned_to']['id'] == '1234'
+
+
+@pytest.fixture(scope='module')
+def delete_assignment_response():
+    # pylint:disable=redefined-outer-name
+    mock_network_response = Mock(DefaultNetworkResponse)
+    mock_network_response.ok = True
+    return mock_network_response
+
+
+def test_delete_policy_return_the_correct_response(
+        test_legal_hold_policy_assignment,
+        mock_box_session,
+        delete_assignment_response,
+):
+    # pylint:disable=redefined-outer-name
+    mock_box_session.delete.return_value = delete_assignment_response
+    response = test_legal_hold_policy_assignment.delete()
+    # pylint:disable=protected-access
+    expected_url = test_legal_hold_policy_assignment.get_url()
+    # pylint:enable = protected-access
+    mock_box_session.delete.assert_called_once_with(expected_url, params={}, expect_json_response=False, headers=None)
+    assert response is True
diff --git a/test/unit/object/test_legal_hold_policy.py b/test/unit/object/test_legal_hold_policy.py
new file mode 100644
index 0000000..4b04f8c
--- /dev/null
+++ b/test/unit/object/test_legal_hold_policy.py
@@ -0,0 +1,151 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+import json
+import pytest
+
+from mock import Mock
+from boxsdk.object.legal_hold_policy import LegalHoldPolicy
+from boxsdk.object.legal_hold_policy_assignment import LegalHoldPolicyAssignment
+from boxsdk.config import API
+from boxsdk.network.default_network import DefaultNetworkResponse
+
+
+@pytest.fixture(scope='module')
+def policy_id_1():
+    return 101
+
+
+@pytest.fixture(scope='module')
+def policy_id_2():
+    return 202
+
+
+@pytest.fixture(scope='module')
+def legal_hold_id_1():
+    return 101
+
+
+@pytest.fixture(scope='module')
+def legal_hold_id_2():
+    return 202
+
+
+@pytest.fixture(scope='module')
+def policies_response(policy_id_1, policy_id_2):
+    # pylint disable=redefined-outer-name
+    mock_network_response = Mock(DefaultNetworkResponse)
+    mock_network_response.json.return_value = {
+        'entries': [
+            {'type': 'legal_hold_policy', 'id': policy_id_1, 'name': 'Test Policy 1'},
+            {'type': 'legal_hold_policy', 'id': policy_id_2, 'name': 'Test Policy 2'}
+        ],
+        'limit': 5,
+    }
+    return mock_network_response
+
+
+@pytest.fixture(scope='module')
+def legal_hold_response(legal_hold_id_1, legal_hold_id_2):
+    # pylint disable=redefined-outer-name
+    mock_network_response = Mock(DefaultNetworkResponse)
+    mock_network_response.json.return_value = {
+        'entries': [
+            {'type': 'legal_hold', 'id': legal_hold_id_1, 'name': 'Test Legal Hold 1'},
+            {'type': 'legal_hold', 'id': legal_hold_id_2, 'name': 'Test Legal Hold 2'}
+        ],
+        'limit': 5,
+    }
+    return mock_network_response
+
+
+def test_assign(test_legal_hold_policy, mock_box_session, test_file):
+    assignment_id = '12345'
+    assigned_at = '2016-05-18T17:38:03-07:00',
+    expected_url = '{0}/legal_hold_policy_assignments'.format(API.BASE_API_URL)
+    expected_body = {
+        'policy_id': test_legal_hold_policy.object_id,
+        'assign_to': {
+            'type': 'file',
+            'id': test_file.object_id
+        }
+    }
+    mock_box_session.post.return_value.json.return_value = {
+        'type': 'legal_hold_policy_assignment',
+        'id': assignment_id,
+        'assigned_at': assigned_at,
+    }
+    new_legal_hold_assignment = test_legal_hold_policy.assign(test_file)
+    mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(expected_body))
+    assert isinstance(new_legal_hold_assignment, LegalHoldPolicyAssignment)
+    assert new_legal_hold_assignment.assigned_at == assigned_at
+    assert new_legal_hold_assignment.id == assignment_id
+
+
+def test_get(test_legal_hold_policy, mock_box_session):
+    created_at = '2016-05-18T17:38:03-07:00',
+    expected_url = '{0}/legal_hold_policies/{1}'.format(API.BASE_API_URL, test_legal_hold_policy.object_id)
+    mock_box_session.get.return_value.json.return_value = {
+        'type': 'legal_hold_policy',
+        'id': test_legal_hold_policy.object_id,
+        'created_at': created_at
+    }
+    legal_hold_policy = test_legal_hold_policy.get()
+    mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None)
+    assert isinstance(legal_hold_policy, LegalHoldPolicy)
+    assert legal_hold_policy.created_at == created_at
+
+
+@pytest.fixture(scope='module')
+def delete_policy_response():
+    # pylint:disable=redefined-outer-name
+    mock_network_response = Mock(DefaultNetworkResponse)
+    mock_network_response.ok = True
+    return mock_network_response
+
+
+def test_delete_policy_return_the_correct_response(
+        test_legal_hold_policy,
+        mock_box_session,
+        delete_policy_response,
+):
+    # pylint:disable=redefined-outer-name
+    mock_box_session.delete.return_value = delete_policy_response
+    response = test_legal_hold_policy.delete()
+    # pylint:disable=protected-access
+    expected_url = test_legal_hold_policy.get_url()
+    # pylint:enable = protected-access
+    mock_box_session.delete.assert_called_once_with(expected_url, params={}, expect_json_response=False, headers=None)
+    assert response is True
+
+
+def test_get_assignments(
+        test_legal_hold_policy,
+        mock_box_session,
+        policies_response,
+        policy_id_1,
+        policy_id_2,
+):
+    # pylint:disable=redefined-outer-name
+    mock_box_session.get.return_value = policies_response
+    assignments = test_legal_hold_policy.get_assignments()
+    for assignment, expected_id in zip(assignments, [policy_id_1, policy_id_2]):
+        assert assignment.object_id == expected_id
+        # pylint:disable=protected-access
+        assert assignment._session == mock_box_session
+
+
+def test_get_file_version_legal_holds(
+        test_legal_hold_policy,
+        mock_box_session,
+        legal_hold_response,
+        legal_hold_id_1,
+        legal_hold_id_2
+):
+    # pylint:disable=redefined-outer-name
+    mock_box_session.get.return_value = legal_hold_response
+    legal_holds = test_legal_hold_policy.get_file_version_legal_holds()
+    for legal_hold, expected_id in zip(legal_holds, [legal_hold_id_1, legal_hold_id_2]):
+        assert legal_hold.object_id == expected_id
+        # pylint:disable=protected-access
+        assert legal_hold._session == mock_box_session