Repository: box/box-python-sdk
Pull request #330 (2018-08-21 21:32:40)

Terms of service v2

boxcla: Hi @carycheng, thanks for the pull request. Before we can merge it, we need you to sign our Contributor License Agreement. You can do so electronically here: http://opensource.box.com/cla

Once you have signed, just add a comment to this pull request saying, "CLA signed". Thanks!
coveralls: ## Pull Request Test Coverage Report for [Build 1196](https://coveralls.io/builds/19683340)

* **68** of **70**   **(97.14%)**  changed or added relevant lines in **3** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage increased (+**0.06%**) to **95.33%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [boxsdk/client/client.py](https://coveralls.io/builds/19683340/source?filename=boxsdk%2Fclient%2Fclient.py#L758) | 13 | 15 | 86.67%
<!-- | **Total:** | **68** | **70** | **97.14%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/19683340/badge)](https://coveralls.io/builds/19683340) |
| :-- | --: |
| Change from base [Build 1148](https://coveralls.io/builds/19605860): |  0.06% |
| Covered Lines: | 2225 |
| Relevant Lines: | 2334 |

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


Patch:
diff --git a/boxsdk/client/client.py b/boxsdk/client/client.py
index 3970ddb..956f175 100644
--- a/boxsdk/client/client.py
+++ b/boxsdk/client/client.py
@@ -743,6 +743,71 @@ def create_group(
             response_object=response,
         )
 
+    def terms_of_service(self, tos_id):
+        """
+        Initialize a :class:`TermsOfService` object, whose box id is tos_id.
+
+        :param tos_id:
+            The box id of the :class:`TermsOfService` object.
+        :type tos_id:
+            `unicode`
+        :return:
+            A :class:`TermsOfService` object with the given terms of service id.
+        :rtype:
+            :class:`TermsOfService`
+        """
+        return self.translator.get('terms_of_service')(session=self._session, object_id=tos_id)
+
+    def terms_of_service_user_status(self, tos_user_status_id):
+        """
+        Initialize a :class:`TermsOfServiceUserStatus` object, whose box id is tos_user_status_id.
+
+        :param tos_user_status_id:
+            The box id of the :class:`TermsOfServiceUserStatus` object.
+        :type tos_id:
+            `unicode`
+        :return:
+            A :class:`TermsOfServiceUserStatus` object with the given terms of service user status id.
+        :rtype:
+            :class:`TermsOfServiceUserStatus`
+        """
+        return self.translator.get('terms_of_service_user_status')(session=self._session, object_id=tos_user_status_id)
+
+    def get_terms_of_services(self, tos_type=None, limit=None, fields=None):
+        """
+        Get the entries in the terms of service using limit-offset paging.
+
+        :param tos_type:
+            Can be set to `managed` or `external` for the type of terms of service.
+        :type tos_type:
+            :class:`TermsOfServiceType`
+        :param: limit
+            The maximum number of items to return. If limit is set to None, then the default
+            limit (returned by Box in the response) is used.
+        :type: limit
+            `int` or None
+        :param fields:
+            List of fields to request
+        :type fields:
+            `Iterable` of `unicode`
+        :returns:
+            An iterator of the entries in the terms of service
+        :rtype:
+            :class:`BoxObjectCollection`
+        """
+        additional_params = {}
+        if tos_type is not None:
+            additional_params['tos_type'] = tos_type
+        return MarkerBasedObjectCollection(
+            session=self._session,
+            url=self._session.get_url('terms_of_services'),
+            additional_params=additional_params,
+            limit=limit,
+            marker=None,
+            fields=fields,
+            return_full_pages=False,
+        )
+
     def task(self, task_id):
         """
         Initialize a :class:`Task` object, whose box id is task_id.
@@ -946,6 +1011,40 @@ def get_retention_policies(
             return_full_pages=False,
         )
 
+    def create_terms_of_service(self, status, tos_type, text):
+        """
+        Create a terms of service.
+
+        :param status:
+            The status of the terms of service.
+        :type status:
+            :class:`TermsOfServiceStatus`
+        :param tos_type:
+            The type of the terms of service. Can be set to `managed` or `external`.
+        :type tos_type:
+            :class:`TermsOfServiceType`
+        :param text:
+            The message of the terms of service.
+        :type text:
+            `unicode`
+        :returns:
+            A newly created :class:`TermsOfService` object
+        :rtype:
+            :class:`TermsOfService`
+        """
+        url = self.get_url('terms_of_services')
+        body = {
+            'status': status,
+            'tos_type': tos_type,
+            'text': text
+        }
+        box_response = self._session.post(url, data=json.dumps(body))
+        response = box_response.json()
+        return self.translator.translate(
+            session=self._session,
+            response_object=response,
+        )
+
     @api_call
     def get_file_version_retentions(
             self,
diff --git a/boxsdk/object/__init__.py b/boxsdk/object/__init__.py
index 53d5d4e..ee03b2a 100644
--- a/boxsdk/object/__init__.py
+++ b/boxsdk/object/__init__.py
@@ -28,6 +28,8 @@
     'retention_policy',
     'retention_policy_assignment',
     'search',
+    'terms_of_service',
+    'terms_of_service_user_status',
     'trash',
     'task',
     'task_assignment',
diff --git a/boxsdk/object/terms_of_service.py b/boxsdk/object/terms_of_service.py
new file mode 100644
index 0000000..f06bd48
--- /dev/null
+++ b/boxsdk/object/terms_of_service.py
@@ -0,0 +1,128 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+import json
+
+from boxsdk.util.text_enum import TextEnum
+from boxsdk.exception import BoxAPIException
+from .base_object import BaseObject
+
+
+class TermsOfServiceType(TextEnum):
+    """An enum of possible terms of service types"""
+    MANAGED = 'managed'
+    EXTERNAL = 'external'
+
+
+class TermsOfServiceStatus(TextEnum):
+    """An enum of possible terms of service status"""
+    ENABLED = 'enabled'
+    DISABLED = 'disabled'
+
+
+class TermsOfService(BaseObject):
+    """Represents a Box terms of service."""
+
+    _item_type = 'terms_of_service'
+
+    def get_user_status(self, user=None):
+        """
+        Get the terms of service user status.
+
+        :param user:
+            This is the user to get the status of the terms of service for. This defaults to current
+            user.
+        :type user:
+            :class:`User` or None
+        :returns:
+            A :class:`TermsOfServiceUserStatus` object
+        :rtype:
+            :class:`TermsOfServiceUserStatus`
+        """
+        url = self._session.get_url('terms_of_service_user_statuses')
+        additional_params = {
+            'tos_id': self.object_id,
+        }
+        if user is not None:
+            additional_params['user_id'] = user.object_id
+        box_response = self._session.get(url, params=additional_params)
+        response_object = box_response.json()
+        response = response_object['entries'][0]
+        return self.translator.translate(
+            session=self._session,
+            response_object=response,
+        )
+
+    def accept(self, user=None):
+        """
+        Accept a terms of service.
+
+        :param user:
+            The :class:`User` to assign the terms of service to.
+        :type user:
+            :class:`User` or None
+        :returns:
+            A newly created :class:`TermsOfServiceUserStatus` object
+        :rtype:
+            :class:`TermsOfServiceUserStatus`
+        """
+        return self.set_user_status(is_accepted=True, user=user)
+
+    def reject(self, user=None):
+        """
+        Reject a terms of service.
+
+        :param user:
+            The :class:`User` to assign the terms of service to.
+        :type user:
+            :class:`User` or None
+        :returns:
+            A newly created :class:`TermsOfServiceUserStatus` object
+        :rtype:
+            :class:`TermsOfServiceUserStatus`
+        """
+        return self.set_user_status(is_accepted=False, user=user)
+
+    def set_user_status(self, is_accepted, user=None):
+        """
+        Create a terms of service user status.
+
+        :param is_accepted:
+            Indicates whether a use has accepted or rejected a terms of service.
+        :type is_accepted:
+            `bool`
+        :param user:
+            The :class:`User` to assign the terms of service to.
+        :type user:
+            :class:`User` or None
+        :returns:
+            A newly created :class:`TermsOfServiceUserStatus` object
+        :rtype:
+            :class:`TermsOfServiceUserStatus`
+        """
+        url = self._session.get_url('terms_of_service_user_statuses')
+        body = {
+            'tos': {
+                'type': self.object_type,
+                'id': self.object_id,
+            },
+            'is_accepted': is_accepted,
+        }
+        if user is not None:
+            body['user'] = {
+                'type': user.object_type,
+                'id': user.object_id,
+            }
+        translated_response = None
+        try:
+            box_response = self._session.post(url, data=json.dumps(body))
+            response = box_response.json()
+            translated_response = self.translator.translate(
+                session=self._session,
+                response_object=response,
+            )
+        except BoxAPIException as err:
+            if err.status == 409:
+                user_status = self.get_user_status(user)
+                translated_response = user_status.update_info({'is_accepted': is_accepted})
+        return translated_response
diff --git a/boxsdk/object/terms_of_service_user_status.py b/boxsdk/object/terms_of_service_user_status.py
new file mode 100644
index 0000000..ea1e315
--- /dev/null
+++ b/boxsdk/object/terms_of_service_user_status.py
@@ -0,0 +1,31 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+from .base_object import BaseObject
+
+
+class TermsOfServiceUserStatus(BaseObject):
+    """Represents a Box terms of service user status."""
+
+    _item_type = 'terms_of_service_user_status'
+
+    def get_url(self, *args):
+        return self._session.get_url('terms_of_service_user_statuses', self._object_id, *args)
+
+    def accept(self):
+        """
+        Accept a term of service.
+        """
+        body = {
+            'is_accepted': True
+        }
+        return self.update_info(data=body)
+
+    def reject(self):
+        """
+        Reject a term of service.
+        """
+        body = {
+            'is_accepted': False
+        }
+        return self.update_info(data=body)
diff --git a/docs/terms_of_service.md b/docs/terms_of_service.md
new file mode 100644
index 0000000..af0dbc1
--- /dev/null
+++ b/docs/terms_of_service.md
@@ -0,0 +1,99 @@
+Terms of Service
+================
+
+Terms of Service allows Box Admins to configure a custom Terms of Service for end users to
+accept/re-accept/decline for custom applications
+
+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+
+
+- [Create a Terms of Service](#create-a-terms-of-service)
+- [Edit a Terms of Service](#edit-a-terms-of-service)
+- [Get Terms of Service](#get-terms-of-service)
+- [List Terms of Service](#list-terms-of-service)
+- [Update User Status on Terms of Service](#update-user-status-on-terms-of-service)
+- [Accept or Decline a Terms of Service](#accept-or-decline-a-terms-of-service)
+- [Get User Status for a Terms of Service](#get-user-status-for-a-terms-of-service)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+Create a Terms of Service
+-------------------------
+
+A terms of service can be created in an enterprise. Please note that only two can be created. One external
+and one managed. If a terms of service already exists please use the update call to change the current
+terms of service.
+
+You can create a custom terms of service by using `client.create_terms_of_service(status, tos_type, text)`.
+
+```python
+from boxsdk.object.terms_of_service import TermsOfServiceType, TermsOfServiceStatus
+terms_of_service = client.create_terms_of_service(TermsOfServiceStatus.ENABLED, TermsOfServiceType.MANAGED, 'Example Text')
+```
+
+Edit a Terms of Service
+-----------------------
+
+To update a terms of service, use `terms_of_service.update_info()`.
+
+```python
+tos_id = '1234'
+update_object = {'text': 'New Text'}
+updated_tos = client.terms_of_service(tos_id).update_info(update_object)
+```
+
+Get Terms of Service
+--------------------
+
+To retrieve information about a terms of service, use `terms_of_service.get()`.
+
+```python
+tos_id = '1234'
+terms_of_service = client.terms_of_service(tos_id).get()
+```
+
+List Terms of Service
+---------------------
+
+You can retrieve an iterable of terms of service in your enterprise by calling `client.get_terms_of_services(tos_type=None, limit=None)`.
+
+```python
+terms_of_services = client.get_terms_of_services()
+for terms_of_service in terms_of_services:
+    # Do something
+```
+
+Update User Status on Terms of Service
+--------------------------------------
+
+To update user status on a terms of service call the `terms_of_service_user_status.update_info(update_object)` method.
+
+```python
+user_status = client.terms_of_service_user_status('12345').update_info({'is_accepted': True})
+```
+
+It is important to note that this will accept or decline a custom terms of service for a user. For a user that has taken action in this terms of service, this will update their status. If the user has never taken action on this terms of service then this will return a 404 Not Found Error.
+
+Accept or Decline a Terms of Service
+------------------------------------
+
+To create user/terms of service association and accept/decline call the  `terms_of_service.set_user_status(is_accepted, user=None)` method.
+
+```python
+user = client.user('22222')
+user_status = client.terms_of_service('11111').set_user_status(is_accepted=True, user=user)
+```
+
+It is important to note that regardless of whether the user has taken action on this terms of service. This will create and update the user status on the terms of service.
+
+Get User Status for a Terms of Service
+-------------------------------------
+
+You can retrieve a terms of service status for a user by calling
+`terms_of_service.get_user_status(user)`.
+
+```python
+user = client.user('11111')
+user_status = client.terms_of_service('1234').get_user_status(user)
+```
\ No newline at end of file


Test patch:
diff --git a/test/unit/client/test_client.py b/test/unit/client/test_client.py
index 838240a..fa63f57 100644
--- a/test/unit/client/test_client.py
+++ b/test/unit/client/test_client.py
@@ -9,10 +9,12 @@
 
 # pylint:disable=redefined-builtin
 # pylint:disable=import-error
+# pylint: disable=too-many-lines
 from six.moves import zip
 # pylint:enable=redefined-builtin
 # pylint:enable=import-error
 
+
 from boxsdk.auth.oauth2 import OAuth2, TokenScope
 from boxsdk.client import Client, DeveloperTokenClient, DevelopmentClient, LoggingClient
 from boxsdk.config import API
@@ -24,6 +26,7 @@
 from boxsdk.object.folder import Folder
 from boxsdk.object.file import File
 from boxsdk.object.group import Group
+from boxsdk.object.terms_of_service import TermsOfService
 from boxsdk.object.user import User
 from boxsdk.object.trash import Trash
 from boxsdk.object.group_membership import GroupMembership
@@ -227,6 +230,30 @@ def create_group_response():
     return mock_network_response
 
 
+@pytest.fixture(scope='module')
+def tos_id_1():
+    return 101
+
+
+@pytest.fixture(scope='module')
+def tos_id_2():
+    return 202
+
+
+@pytest.fixture(scope='module')
+def terms_of_services_response(tos_id_1, tos_id_2):
+    # pylint:disable=redefined-outer-name
+    mock_network_response = Mock(DefaultNetworkResponse)
+    mock_network_response.json.return_value = {
+        'entries': [
+            {'type': 'terms_of_service', 'id': tos_id_1},
+            {'type': 'terms_of_service', 'id': tos_id_2},
+        ],
+        'total_count': 2,
+    }
+    return mock_network_response
+
+
 @pytest.fixture(scope='module')
 def create_user_response():
     # pylint:disable=redefined-outer-name
@@ -605,6 +632,56 @@ def test_create_enterprise_user_returns_the_correct_user_object(mock_client, moc
     assert new_user.name == test_user_name
 
 
+def test_create_terms_of_service(mock_client, mock_box_session):
+    # pylint:disable=redefined-outer-name
+    expected_url = "{0}/terms_of_services".format(API.BASE_API_URL)
+    test_text = 'This is a test text'
+    test_tos_type = 'external'
+    test_status = 'enabled'
+    value = json.dumps({
+        'status': 'enabled',
+        'tos_type': 'external',
+        'text': 'This is a test text',
+    })
+    mock_box_session.post.return_value.json.return_value = {
+        'type': 'terms_of_service',
+        'id': '12345',
+        'status': test_status,
+        'tos_type': test_tos_type,
+        'text': test_text,
+    }
+    new_terms_of_service = mock_client.create_terms_of_service('enabled', 'external', 'This is a test text')
+    mock_box_session.post.assert_called_once_with(expected_url, data=value)
+    assert isinstance(new_terms_of_service, TermsOfService)
+    assert new_terms_of_service.type == 'terms_of_service'
+    assert new_terms_of_service.id == '12345'
+    assert new_terms_of_service.status == test_status
+    assert new_terms_of_service.tos_type == test_tos_type
+    assert new_terms_of_service.text == test_text
+
+
+def test_get_all_terms_of_services(mock_client, mock_box_session):
+    expected_url = "{0}/terms_of_services".format(API.BASE_API_URL)
+    tos_body = {
+        'type': 'terms_of_service',
+        'id': '12345',
+        'status': 'enabled',
+        'tos_type': 'external',
+    }
+    mock_box_session.get.return_value.json.return_value = {
+        'total_count': 1,
+        'entries': [tos_body],
+    }
+    services = mock_client.get_terms_of_services(tos_type='external')
+    service = services.next()
+    mock_box_session.get.assert_called_once_with(expected_url, params={'tos_type': 'external'})
+    assert isinstance(service, TermsOfService)
+    assert service.type == 'terms_of_service'
+    assert service.id == '12345'
+    assert service.status == 'enabled'
+    assert service.tos_type == 'external'
+
+
 def test_create_webhook_returns_the_correct_policy_object(
         test_item_and_response,
         test_webhook,
diff --git a/test/unit/object/conftest.py b/test/unit/object/conftest.py
index ef1ea1f..6e9bf52 100644
--- a/test/unit/object/conftest.py
+++ b/test/unit/object/conftest.py
@@ -22,6 +22,8 @@
 from boxsdk.object.retention_policy import RetentionPolicy
 from boxsdk.object.retention_policy_assignment import RetentionPolicyAssignment
 from boxsdk.object.search import Search
+from boxsdk.object.terms_of_service import TermsOfService
+from boxsdk.object.terms_of_service_user_status import TermsOfServiceUserStatus
 from boxsdk.object.collaboration_whitelist import CollaborationWhitelist
 from boxsdk.object.collaboration_whitelist_entry import CollaborationWhitelistEntry
 from boxsdk.object.collaboration_whitelist_exempt_target import CollaborationWhitelistExemptTarget
@@ -30,7 +32,6 @@
 from boxsdk.object.task_assignment import TaskAssignment
 from boxsdk.object.web_link import WebLink
 
-
 # pylint:disable=redefined-outer-name
 
 
@@ -160,6 +161,16 @@ def test_search(mock_box_session):
     return Search(mock_box_session)
 
 
+@pytest.fixture()
+def test_terms_of_service(mock_box_session, mock_object_id):
+    return TermsOfService(mock_box_session, mock_object_id)
+
+
+@pytest.fixture()
+def test_terms_of_service_user_status(mock_box_session, mock_object_id):
+    return TermsOfServiceUserStatus(mock_box_session, mock_object_id)
+
+
 @pytest.fixture()
 def test_webhook(mock_box_session, mock_object_id):
     return Webhook(mock_box_session, mock_object_id)
diff --git a/test/unit/object/test_terms_of_service.py b/test/unit/object/test_terms_of_service.py
new file mode 100644
index 0000000..b15371a
--- /dev/null
+++ b/test/unit/object/test_terms_of_service.py
@@ -0,0 +1,170 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+import json
+
+from boxsdk.exception import BoxAPIException
+from boxsdk.config import API
+from boxsdk.object.terms_of_service import TermsOfService
+from boxsdk.object.terms_of_service_user_status import TermsOfServiceUserStatus
+
+
+def test_get(test_terms_of_service, mock_box_session):
+    created_at = '2016-05-18T17:38:03-07:00',
+    expected_url = '{0}/terms_of_services/{1}'.format(API.BASE_API_URL, test_terms_of_service.object_id)
+    mock_box_session.get.return_value.json.return_value = {
+        'type': 'terms_of_service',
+        'id': test_terms_of_service.object_id,
+        'created_at': created_at,
+    }
+    terms_of_service = test_terms_of_service.get()
+    mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None)
+    assert isinstance(terms_of_service, TermsOfService)
+    assert terms_of_service.created_at == created_at
+    assert terms_of_service.id == test_terms_of_service.object_id
+    assert terms_of_service.type == test_terms_of_service.object_type
+
+
+def test_update(test_terms_of_service, mock_box_session):
+    new_text = 'This is new text'
+    expected_url = '{0}/terms_of_services/{1}'.format(API.BASE_API_URL, test_terms_of_service.object_id)
+    mock_box_session.put.return_value.json.return_value = {
+        'type': 'terms_of_service',
+        'id': test_terms_of_service.object_id,
+        'text': new_text,
+    }
+    data = {
+        'text': new_text,
+    }
+    updated_terms_of_service = test_terms_of_service.update_info(data)
+    mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), headers=None, params=None)
+    assert isinstance(updated_terms_of_service, TermsOfService)
+    assert updated_terms_of_service.text == new_text
+    assert updated_terms_of_service.id == test_terms_of_service.object_id
+    assert updated_terms_of_service.type == test_terms_of_service.object_type
+
+
+def test_accept_terms_of_service(test_terms_of_service, test_terms_of_service_user_status, mock_user, mock_box_session):
+    # pylint:disable=redefined-outer-name
+    expected_url = "{0}/terms_of_service_user_statuses".format(API.BASE_API_URL)
+    created_at = '2016-05-18T17:38:03-07:00',
+    value = json.dumps({
+        'tos': {
+            'type': 'terms_of_service',
+            'id': test_terms_of_service.object_id,
+        },
+        'is_accepted': True,
+        'user': {
+            'type': mock_user.object_type,
+            'id': mock_user.object_id,
+        },
+    })
+    mock_box_session.post.return_value.json.return_value = {
+        'type': 'terms_of_service_user_status',
+        'id': test_terms_of_service_user_status.object_id,
+        'created_at': created_at,
+    }
+    new_terms_of_service_user_status = test_terms_of_service.accept(mock_user)
+    mock_box_session.post.assert_called_once_with(expected_url, data=value)
+    assert isinstance(new_terms_of_service_user_status, TermsOfServiceUserStatus)
+    assert new_terms_of_service_user_status.id == test_terms_of_service_user_status.object_id
+    assert new_terms_of_service_user_status.type == test_terms_of_service_user_status.object_type
+    assert new_terms_of_service_user_status.created_at == created_at
+
+
+def test_reject_terms_of_service(test_terms_of_service, test_terms_of_service_user_status, mock_user, mock_box_session):
+    # pylint:disable=redefined-outer-name
+    expected_url = "{0}/terms_of_service_user_statuses".format(API.BASE_API_URL)
+    created_at = '2016-05-18T17:38:03-07:00',
+    value = json.dumps({
+        'tos': {
+            'type': 'terms_of_service',
+            'id': test_terms_of_service.object_id,
+        },
+        'is_accepted': False,
+        'user': {
+            'type': mock_user.object_type,
+            'id': mock_user.object_id,
+        },
+    })
+    mock_box_session.post.return_value.json.return_value = {
+        'type': 'terms_of_service_user_status',
+        'id': test_terms_of_service_user_status.object_id,
+        'created_at': created_at,
+    }
+    new_terms_of_service_user_status = test_terms_of_service.reject(mock_user)
+    mock_box_session.post.assert_called_once_with(expected_url, data=value)
+    assert isinstance(new_terms_of_service_user_status, TermsOfServiceUserStatus)
+    assert new_terms_of_service_user_status.id == test_terms_of_service_user_status.object_id
+    assert new_terms_of_service_user_status.type == test_terms_of_service_user_status.object_type
+    assert new_terms_of_service_user_status.created_at == created_at
+
+
+def test_get_user_status(test_terms_of_service, mock_user, test_terms_of_service_user_status, mock_box_session):
+    expected_url = "{0}/terms_of_service_user_statuses".format(API.BASE_API_URL)
+    created_at = '2016-05-18T17:38:03-07:00'
+    expected_params = {
+        'tos_id': test_terms_of_service.object_id,
+        'user_id': mock_user.object_id,
+    }
+    mock_user_status = {
+        'type': 'terms_of_service_user_status',
+        'id': test_terms_of_service_user_status.object_id,
+        'created_at': created_at,
+    }
+    mock_box_session.get.return_value.json.return_value = {
+        'limit': 500,
+        'entries': [mock_user_status],
+    }
+    new_terms_of_service_user_status = test_terms_of_service.get_user_status(user=mock_user)
+    mock_box_session.get.assert_called_once_with(expected_url, params=expected_params)
+    assert isinstance(new_terms_of_service_user_status, TermsOfServiceUserStatus)
+    assert new_terms_of_service_user_status.type == 'terms_of_service_user_status'
+    assert new_terms_of_service_user_status.id == test_terms_of_service_user_status.object_id
+    assert new_terms_of_service_user_status.created_at == created_at
+
+
+def test_set_user_status(test_terms_of_service, mock_user, mock_box_session):
+    expected_post_url = "{0}/terms_of_service_user_statuses".format(API.BASE_API_URL)
+    expected_put_url = "{0}/terms_of_service_user_statuses/{1}".format(API.BASE_API_URL, test_terms_of_service.object_id)
+    post_value = json.dumps({
+        'tos': {
+            'type': test_terms_of_service.object_type,
+            'id': test_terms_of_service.object_id,
+        },
+        'is_accepted': True,
+        'user': {
+            'type': mock_user.object_type,
+            'id': mock_user.object_id,
+        },
+    })
+    put_value = json.dumps({
+        'is_accepted': True
+    })
+    mock_box_session.post.side_effect = [BoxAPIException(status=409, message="Conflict")]
+    mock_box_session.get.return_value.json.return_value = {
+        'entries': [
+            {
+                'type': 'terms_of_service_user_status',
+                'id': '42',
+            },
+        ],
+    }
+    mock_box_session.put.return_value.json.return_value = {
+        'type': 'terms_of_service_user_status',
+        'id': '12345',
+        'tos': {
+            'type': test_terms_of_service.object_type,
+            'id': test_terms_of_service.object_id,
+        },
+        'is_accepted': True,
+    }
+    new_terms_of_service_user_status = test_terms_of_service.set_user_status(True, mock_user)
+    mock_box_session.post.assert_called_once_with(expected_post_url, data=post_value)
+    mock_box_session.put.assert_called_once_with(expected_put_url, data=put_value, headers=None, params=None)
+    assert isinstance(new_terms_of_service_user_status, TermsOfServiceUserStatus)
+    assert new_terms_of_service_user_status.type == 'terms_of_service_user_status'
+    assert new_terms_of_service_user_status.id == '12345'
+    assert new_terms_of_service_user_status.tos['type'] == test_terms_of_service.object_type
+    assert new_terms_of_service_user_status.tos['id'] == test_terms_of_service.object_id
+    assert new_terms_of_service_user_status.is_accepted is True
diff --git a/test/unit/object/test_terms_of_service_user_status.py b/test/unit/object/test_terms_of_service_user_status.py
new file mode 100644
index 0000000..c7f5e5c
--- /dev/null
+++ b/test/unit/object/test_terms_of_service_user_status.py
@@ -0,0 +1,55 @@
+# coding: utf-8
+from __future__ import unicode_literals
+
+import json
+
+from boxsdk.object.terms_of_service_user_status import TermsOfServiceUserStatus
+from boxsdk.config import API
+
+
+def test_get(test_terms_of_service_user_status, mock_box_session):
+    created_at = '2016-05-18T17:38:03-07:00',
+    expected_url = '{0}/terms_of_service_user_statuses/{1}'.format(API.BASE_API_URL, test_terms_of_service_user_status.object_id)
+    mock_box_session.get.return_value.json.return_value = {
+        'type': 'terms_of_service_user_status',
+        'id': test_terms_of_service_user_status.object_id,
+        'created_at': created_at
+    }
+    terms_of_service = test_terms_of_service_user_status.get()
+    mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None)
+    assert isinstance(terms_of_service, TermsOfServiceUserStatus)
+    assert terms_of_service.type == test_terms_of_service_user_status.object_type
+    assert terms_of_service.id == test_terms_of_service_user_status.object_id
+    assert terms_of_service.created_at == created_at
+
+
+def test_accept(test_terms_of_service_user_status, mock_box_session):
+    expected_url = '{0}/terms_of_service_user_statuses/{1}'.format(API.BASE_API_URL, test_terms_of_service_user_status.object_id)
+    mock_box_session.put.return_value.json.return_value = {
+        'type': test_terms_of_service_user_status.object_type,
+        'id': test_terms_of_service_user_status.object_id,
+        'is_accepted': True
+    }
+    data = {'is_accepted': True}
+    terms_of_service_user_status = test_terms_of_service_user_status.accept()
+    mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), headers=None, params=None)
+    assert isinstance(terms_of_service_user_status, TermsOfServiceUserStatus)
+    assert terms_of_service_user_status.type == test_terms_of_service_user_status.object_type
+    assert terms_of_service_user_status.id == test_terms_of_service_user_status.object_id
+    assert terms_of_service_user_status.is_accepted is True
+
+
+def test_reject(test_terms_of_service_user_status, mock_box_session):
+    expected_url = '{0}/terms_of_service_user_statuses/{1}'.format(API.BASE_API_URL, test_terms_of_service_user_status.object_id)
+    mock_box_session.put.return_value.json.return_value = {
+        'type': 'terms_of_service_user_status',
+        'id': test_terms_of_service_user_status.object_id,
+        'is_accepted': False
+    }
+    data = {'is_accepted': False}
+    terms_of_service_user_status = test_terms_of_service_user_status.reject()
+    mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), headers=None, params=None)
+    assert isinstance(terms_of_service_user_status, TermsOfServiceUserStatus)
+    assert terms_of_service_user_status.type == test_terms_of_service_user_status.object_type
+    assert terms_of_service_user_status.id == test_terms_of_service_user_status.object_id
+    assert terms_of_service_user_status.is_accepted is False