Repository: box/box-python-sdk

Issue:
Finish making breaking changes to paging APIs
We should be using the new paging wrappers.

Also do one final audit of the new paging wrappers to make sure that the interface is what we want.

Discussion:
boxcla: Verified that @mattwiller has signed the CLA. Thanks for the pull request!

Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index e39f426..14dd6d2 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -43,6 +43,22 @@ Release History
 - Comparing two objects (e.g. a ``File`` and a ``Folder``) that have the same Box ID but different types with ``==``
   will now correctly return `False`.
 
+- The following methods now return iterators over the entire collection of returned objects, rather than
+  a single page:
+
+  + ``client.users()``
+  + ``client.groups()``
+  + ``client.search()``
+  + ``folder.get_items()``
+
+  Since ``folder.get_items()`` now returns an iterator, ``folder.get_items_limit_offset()`` and
+  ``folder.get_items_marker()`` have been removed.  To use marker based paging with ``folder.get_items()``,
+  pass the ``use_marker=True`` parameter and optionally specify a ``marker`` parameter to begin paging from that
+  point in the collection.
+
+  Additionally, ``group.membership()`` has been renamed to ``group.get_memberships()``, and returns an iterator of
+  membership objects.  This method no longer provides the option to return tuples with paging information.
+
 **Features**
 
 - Added more flexibility to the object translation system:
diff --git a/boxsdk/client/client.py b/boxsdk/client/client.py
index 3262c4f..c96ca3b 100644
--- a/boxsdk/client/client.py
+++ b/boxsdk/client/client.py
@@ -198,7 +198,7 @@ def collections(self, limit=None, offset=0, fields=None):
         )
 
     @api_call
-    def users(self, limit=None, offset=0, filter_term=None):
+    def users(self, limit=None, offset=0, filter_term=None, user_type=None, fields=None):
         """
         Get a list of all users for the Enterprise along with their user_id, public_name, and login.
 
@@ -214,25 +214,38 @@ def users(self, limit=None, offset=0, filter_term=None):
             Filters the results to only users starting with the filter_term in either the name or the login.
         :type filter_term:
             `unicode` or None
+        :param user_type:
+            Filters the results to only users of the given type: 'managed', 'external', or 'all'.
+        :type user_type:
+            `unicode` or None
+        :param fields:
+            List of fields to request on the :class:`User` objects.
+        :type fields:
+            `Iterable` of `unicode`
         :return:
             The list of all users in the enterprise.
         :rtype:
             `list` of :class:`User`
+        :returns:
+            An iterator on the user's recent items
+        :rtype:
+            :class:`MarkerBasedObjectCollection`
         """
         url = self.get_url('users')
-        params = dict(offset=offset)
-        if limit is not None:
-            params['limit'] = limit
-        if filter_term is not None:
-            params['filter_term'] = filter_term
-        box_response = self._session.get(url, params=params)
-        response = box_response.json()
-        user_class = self.translator.translate('user')
-        return [user_class(
+        additional_params = {}
+        if filter_term:
+            additional_params['filter_term'] = filter_term
+        if user_type:
+            additional_params['user_type'] = user_type
+        return LimitOffsetBasedObjectCollection(
+            url=url,
             session=self._session,
-            object_id=item['id'],
-            response_object=item,
-        ) for item in response['entries']]
+            additional_params=additional_params,
+            limit=limit,
+            offset=offset,
+            fields=fields,
+            return_full_pages=False,
+        )
 
     @api_call
     def search(
@@ -322,24 +335,43 @@ def group_membership(self, group_membership_id):
         )
 
     @api_call
-    def groups(self):
+    def groups(self, name=None, offset=0, limit=None, fields=None):
         """
         Get a list of all groups for the current user.
 
+        :param name:
+            Filter on the name of the groups to return.
+        :type name:
+            `unicode` or None
+        :param limit:
+            The maximum number of groups to return. If not specified, the Box API will determine an appropriate limit.
+        :type limit:
+            `int` or None
+        :param offset:
+            The group index at which to start the response.
+        :type offset:
+            `int`
+        :param fields:
+            List of fields to request on the :class:`Group` objects.
+        :type fields:
+            `Iterable` of `unicode`
         :return:
-            The list of all groups.
+            The collection of all groups.
         :rtype:
-            `list` of :class:`Group`
+            `Iterable` of :class:`Group`
         """
         url = self.get_url('groups')
-        box_response = self._session.get(url)
-        response = box_response.json()
-        group_class = self.translator.translate('group')
-        return [group_class(
+        additional_params = {}
+        if name:
+            additional_params['name'] = name
+        return LimitOffsetBasedObjectCollection(
+            url=url,
             session=self._session,
-            object_id=item['id'],
-            response_object=item,
-        ) for item in response['entries']]
+            limit=limit,
+            offset=offset,
+            fields=fields,
+            return_full_pages=False,
+        )
 
     @api_call
     def create_group(self, name):
diff --git a/boxsdk/object/folder.py b/boxsdk/object/folder.py
index e6097d9..49d8272 100644
--- a/boxsdk/object/folder.py
+++ b/boxsdk/object/folder.py
@@ -124,98 +124,72 @@ def _get_accelerator_upload_url_fow_new_uploads(self):
         return self._get_accelerator_upload_url()
 
     @api_call
-    def get_items(self, limit, offset=0, fields=None):
-        """Get the items in a folder.
+    def get_items(self, limit=None, offset=0, marker=None, use_marker=False, sort=None, direction=None, fields=None):
+        """
+        Get the items in a folder.
 
         :param limit:
             The maximum number of items to return.
         :type limit:
-            `int`
+            `int` or None
         :param offset:
-            The index at which to start returning items.
+            The index at which to start returning items when using offset-based pagin.
         :type offset:
             `int`
+        :param use_marker:
+            Whether to use marker-based paging instead of offset-based paging, defaults to False.
+        :type use_marker:
+            `bool`
+        :param marker:
+            The paging marker to start returning items from when using marker-based paging.
+        :type marker:
+            `unicode` or None
+        :param sort:
+            Item field to sort results on: 'id', 'name', or 'date'.
+        :type sort':
+            `unicode` or None
+        :param direction:
+            Sort direction for the items returned.
+        :type direction:
+            `unicode` or None
         :param fields:
             List of fields to request.
         :type fields:
             `Iterable` of `unicode`
         :returns:
-            A list of items in the folder.
+            The collection of items in the folder.
         :rtype:
-            `list` of :class:`Item`
+            `Iterable` of :class:`Item`
         """
         url = self.get_url('items')
-        params = {
-            'limit': limit,
-            'offset': offset,
-        }
-        if fields:
-            params['fields'] = ','.join(fields)
-        box_response = self._session.get(url, params=params)
-        response = box_response.json()
-        return [self.translator.translate(item['type'])(self._session, item['id'], item) for item in response['entries']]
-
-    @api_call
-    def get_items_limit_offset(self, limit=None, offset=0, fields=None):
-        """
-        Get the items in a folder using limit-offset paging.
+        additional_params = {}
+        if limit is not None:
+            additional_params['limit'] = limit
+        if sort:
+            additional_params['sort'] = sort
+        if direction:
+            additional_params['direction'] = direction
+
+        if use_marker:
+            additional_params['usemarker'] = True
+            return MarkerBasedObjectCollection(
+                url=url,
+                session=self._session,
+                limit=limit,
+                marker=marker,
+                fields=fields,
+                additional_params=additional_params,
+                return_full_pages=False,
+            )
 
-        :param limit:
-            The maximum number of items to return per page. If not specified, then will use the server-side default.
-        :type limit:
-            `int` or None
-        :param offset:
-            The index at which to start returning items.
-        :type offset:
-            `int`
-        :param fields:
-            List of fields to request.
-        :type fields:
-            `Iterable` of `unicode`
-        :returns:
-            An iterator of the items in the folder.
-        :rtype:
-            :class:`BoxObjectCollection`
-        """
         return LimitOffsetBasedObjectCollection(
-            self.session,
-            self.get_url('items'),
+            url=url,
+            session=self._session,
             limit=limit,
-            fields=fields,
             offset=offset,
-            return_full_pages=False,
-        )
-
-    @api_call
-    def get_items_marker(self, limit=None, marker=None, fields=None):
-        """
-        Get the items in a folder using marker-based paging.
-
-        :param limit:
-            The maximum number of items to return per page. If not specified, then will use the server-side default.
-        :type limit:
-            `int` or None
-        :param marker:
-            The offset index to start paging from.
-        :type marker:
-            `str` or None
-        :param fields:
-            List of fields to request.
-        :type fields:
-            `Iterable` of `unicode`
-        :returns:
-            An iterator of the items in the folder.
-        :rtype:
-            :class:`BoxObjectCollection`
-        """
-        return MarkerBasedObjectCollection(
-            self.session,
-            self.get_url('items'),
-            limit=limit,
             fields=fields,
-            marker=marker,
+            additional_params=additional_params,
             return_full_pages=False,
-            supports_limit_offset_paging=True,
         )
 
     @api_call
diff --git a/boxsdk/object/group.py b/boxsdk/object/group.py
index e960df4..15ae8b8 100644
--- a/boxsdk/object/group.py
+++ b/boxsdk/object/group.py
@@ -1,10 +1,10 @@
 # coding: utf-8
 
 from __future__ import unicode_literals, absolute_import
-from functools import partial
 import json
 
 from .base_object import BaseObject
+from ..pagination.limit_offset_based_object_collection import LimitOffsetBasedObjectCollection
 from ..util.api_call_decorator import api_call
 
 
@@ -14,45 +14,32 @@ class Group(BaseObject):
     _item_type = 'group'
 
     @api_call
-    def membership(self, starting_index=0, limit=100, include_page_info=False):
+    def get_memberships(self, offset=0, limit=None, fields=None):
         """
-        A generator over all the members of this Group. The paging in the API is transparently implemented
-        inside the generator. By adjusting the page_size, the caller can control the chattiness of the API. Caller
-        can also implement their owning paging and/or control exactly when an API is called by
-        using the 'include_page_info' param as follows:
+        Get the membership records for the group, which indicate which users are included in the group.
 
-            for group, page_size, index in group.membership(..., include_page_info=True):
-                # when index + 1 == page_size, the next iteration of this loop will
-                # trigger an API call, unless we've reached the end of *all* the data.
-                pass
-
-        :param starting_index:
+        :param offset:
             The index at which to begin.
-        :type starting_index:
+        :type offset:
             `int`
         :param limit:
             The maximum number of items to return in a page.
         :type limit:
-            `int`
+            `int` or None
         :returns:
-            A generator of GroupMembership instances. Or, if include_page_info
-            is True, it is a generator of 3-tuples, where each tuple is
-                1) GroupMembership instance
-                2) Number of GroupMemberships returned by the last paged API call
-                3) Index of *this* GroupMembership instance in the current page.
+            The collection of membership objects for the group.
         :rtype:
-            `generator` of :class:`GroupMembership` or, if include_page_info
-            is True then `tuple` of (:class:`GroupMembership`, `int`, `int`)
+            `Iterable` of :class:`GroupMembership`
         """
         url = self.get_url('memberships')
-
-        membership_factory = partial(self.translator.translate("group_membership"), group=self)
-        for group_membership_tuple in self._paging_wrapper(url, starting_index, limit, membership_factory):
-            if include_page_info:
-                yield group_membership_tuple
-            else:
-                group_membership, _, _ = group_membership_tuple
-                yield group_membership
+        return LimitOffsetBasedObjectCollection(
+            self._session,
+            url,
+            limit=limit,
+            offset=offset,
+            fields=fields,
+            return_full_pages=False
+        )
 
     @api_call
     def add_member(self, user, role):
diff --git a/boxsdk/object/search.py b/boxsdk/object/search.py
index 6951bb6..5da561a 100644
--- a/boxsdk/object/search.py
+++ b/boxsdk/object/search.py
@@ -5,6 +5,7 @@
 import json
 
 from .base_endpoint import BaseEndpoint
+from ..pagination.limit_offset_based_object_collection import LimitOffsetBasedObjectCollection
 from ..util.api_call_decorator import api_call
 
 
@@ -161,13 +162,14 @@ def make_single_metadata_filter(template_key, scope):
     def search(
             self,
             query,
-            limit=100,
+            limit=None,
             offset=0,
             ancestor_folders=None,
             file_extensions=None,
             metadata_filters=None,
             result_type=None,
             content_types=None,
+            fields=None,
             **kwargs
     ):
         """
@@ -205,30 +207,35 @@ def search(
             Which content types to search. Valid types include name, description, file_content, comments, and tags.
         :type content_types:
             `Iterable` of `unicode`
+        :param fields:
+            Fields to include on the returned items.
+        :type fields:
+            `Iterable` of `unicode`
         :return:
-            A list of items that match the search query.
+            The collection of items that match the search query.
         :rtype:
-            `list` of :class:`Item`
+            `Iterable` of :class:`Item`
         """
         url = self.get_url()
-        params = {
-            'query': query,
-            'limit': limit,
-            'offset': offset,
-        }
+        additional_params = {'query': query}
         if ancestor_folders:
-            params.update({
-                'ancestor_folder_ids': ','.join([folder.object_id for folder in ancestor_folders])
-            })
+            additional_params['ancestor_folder_ids'] = ','.join([folder.object_id for folder in ancestor_folders])
         if file_extensions:
-            params.update({'file_extensions': ','.join(file_extensions)})
+            additional_params['file_extensions'] = ','.join(file_extensions)
         if metadata_filters:
-            params.update({'mdfilters': json.dumps(metadata_filters.as_list())})
+            additional_params['mdfilters'] = json.dumps(metadata_filters.as_list())
         if content_types:
-            params.update({'content_types': ','.join(content_types)})
+            additional_params['content_types'] = ','.join(content_types)
         if result_type:
-            params.update({'type': result_type})
-        params.update(kwargs)
-        box_response = self._session.get(url, params=params)
-        response = box_response.json()
-        return [self.translator.translate(item['type'])(self._session, item['id'], item) for item in response['entries']]
+            additional_params['type'] = result_type
+        additional_params.update(kwargs)
+
+        return LimitOffsetBasedObjectCollection(
+            self._session,
+            url,
+            limit=limit,
+            offset=offset,
+            fields=fields,
+            additional_params=additional_params,
+            return_full_pages=False,
+        )


Test patch:
diff --git a/test/functional/test_delete.py b/test/functional/test_delete.py
index b350436..3198ed2 100644
--- a/test/functional/test_delete.py
+++ b/test/functional/test_delete.py
@@ -12,13 +12,21 @@ def test_upload_then_delete(box_client, test_file_path, test_file_content, file_
     with patch('boxsdk.object.folder.open', streamable_mock_open(read_data=test_file_content), create=True):
         file_object = box_client.folder('0').upload(test_file_path, file_name)
     assert file_object.delete()
-    assert not box_client.folder('0').get_items(1)
+    folder_items = box_client.folder('0').get_items(1)
+    item_count = 0
+    for _ in folder_items:
+        item_count += 1
+    assert item_count == 0
 
 
 def test_create_folder_then_update_info(box_client, folder_name):
     folder = box_client.folder('0').create_subfolder(folder_name)
     assert folder.delete()
-    assert not box_client.folder('0').get_items(1)
+    folder_items = box_client.folder('0').get_items(1)
+    item_count = 0
+    for _ in folder_items:
+        item_count += 1
+    assert item_count == 0
 
 
 @pytest.mark.parametrize('constructor', [Client.file, Client.folder])
diff --git a/test/functional/test_file_upload_update_download.py b/test/functional/test_file_upload_update_download.py
index 3b8d2d4..23bd6bc 100644
--- a/test/functional/test_file_upload_update_download.py
+++ b/test/functional/test_file_upload_update_download.py
@@ -18,9 +18,13 @@ def test_upload_then_update(box_client, test_file_path, test_file_content, updat
         else test_file_content
     assert file_content == expected_file_content
     folder_items = box_client.folder('0').get_items(100)
-    assert len(folder_items) == 1
-    assert folder_items[0].object_id == file_object.object_id
-    assert folder_items[0].name == file_object.name
+    item = folder_items.next()
+    item_count = 1
+    for _ in folder_items:
+        item_count += 1
+    assert item_count == 1
+    assert item.object_id == file_object.object_id
+    assert item.name == file_object.name
     with patch('boxsdk.object.file.open', streamable_mock_open(read_data=update_file_content), create=True):
         updated_file_object = file_object.update_contents(test_file_path)
     assert updated_file_object.name == file_name
@@ -32,9 +36,13 @@ def test_upload_then_update(box_client, test_file_path, test_file_content, updat
         else update_file_content
     assert file_content == expected_file_content
     folder_items = box_client.folder('0').get_items(100)
-    assert len(folder_items) == 1
-    assert folder_items[0].object_id == file_object.object_id
-    assert folder_items[0].name == file_object.name
+    item = folder_items.next()
+    item_count = 1
+    for _ in folder_items:
+        item_count += 1
+    assert item_count == 1
+    assert item.object_id == file_object.object_id
+    assert item.name == file_object.name
 
 
 def test_upload_then_download(box_client, test_file_path, test_file_content, file_name):
diff --git a/test/functional/test_item_info.py b/test/functional/test_item_info.py
index 5f3d414..74402bc 100644
--- a/test/functional/test_item_info.py
+++ b/test/functional/test_item_info.py
@@ -57,8 +57,14 @@ def _test_create_then_move(box_client, item):
     item = item.get()
     assert item.name == item_name
     assert item.parent['id'] == move_target.object_id
-    assert len(box_client.folder('0').get_items(10)) == 1
-    assert len(move_target.get_items(10)) == 1
+    item_count = 0
+    for _ in box_client.folder('0').get_items(10):
+        item_count += 1
+    assert item_count == 1
+    item_count = 0
+    for _ in move_target.get_items(10):
+        item_count += 1
+    assert item_count == 1
 
 
 def test_create_folder_then_copy(box_client, created_subfolder):
@@ -80,8 +86,14 @@ def _test_create_then_copy(box_client, item):
     assert item.id != copied_item.id
     assert item.name == copied_item.name
     assert copied_item.parent['id'] == copy_target.object_id
-    assert len(box_client.folder('0').get_items(10)) == 2
-    assert len(copy_target.get_items(10)) == 1
+    item_count = 0
+    for _ in box_client.folder('0').get_items(10):
+        item_count += 1
+    assert item_count == 2
+    item_count = 0
+    for _ in copy_target.get_items(10):
+        item_count += 1
+    assert item_count == 1
 
 
 @pytest.mark.parametrize('constructor', [Client.file, Client.folder])
diff --git a/test/unit/client/test_client.py b/test/unit/client/test_client.py
index 7dfcc9f..4997a76 100644
--- a/test/unit/client/test_client.py
+++ b/test/unit/client/test_client.py
@@ -70,9 +70,15 @@ def marker_id():
 def users_response(user_id_1, user_id_2):
     # pylint:disable=redefined-outer-name
     mock_network_response = Mock(DefaultNetworkResponse)
-    mock_network_response.json.return_value = {'entries': [
-        {'type': 'user', 'id': user_id_1}, {'type': 'user', 'id': user_id_2}
-    ]}
+    mock_network_response.json.return_value = {
+        'entries': [
+            {'type': 'user', 'id': user_id_1},
+            {'type': 'user', 'id': user_id_2}
+        ],
+        'limit': 100,
+        'offset': 0,
+        'total_count': 2
+    }
     return mock_network_response
 
 
@@ -98,10 +104,15 @@ def group_id_2():
 def groups_response(group_id_1, group_id_2):
     # pylint:disable=redefined-outer-name
     mock_network_response = Mock(DefaultNetworkResponse)
-    mock_network_response.json.return_value = {'entries': [
-        {'type': 'group', 'id': group_id_1, 'name': text_type(group_id_1)},
-        {'type': 'group', 'id': group_id_2, 'name': text_type(group_id_2)},
-    ]}
+    mock_network_response.json.return_value = {
+        'entries': [
+            {'type': 'group', 'id': group_id_1, 'name': text_type(group_id_1)},
+            {'type': 'group', 'id': group_id_2, 'name': text_type(group_id_2)},
+        ],
+        'limit': 100,
+        'offset': 0,
+        'total_count': 2
+    }
     return mock_network_response
 
 
@@ -145,9 +156,15 @@ def shared_item_response(request):
 def search_response(file_id, folder_id):
     # pylint:disable=redefined-outer-name
     mock_network_response = Mock(DefaultNetworkResponse)
-    mock_network_response.json.return_value = {'entries': [
-        {'type': 'file', 'id': file_id}, {'type': 'folder', 'id': folder_id}
-    ]}
+    mock_network_response.json.return_value = {
+        'entries': [
+            {'type': 'file', 'id': file_id},
+            {'type': 'folder', 'id': folder_id}
+        ],
+        'limit': 100,
+        'offset': 0,
+        'total_count': 2
+    }
     return mock_network_response
 
 
@@ -217,9 +234,9 @@ def test_users_return_the_correct_user_objects(
         expected_params['limit'] = users_limit
     if users_filter_term is not None:
         expected_params['filter_term'] = users_filter_term
+    assert users.next().object_id == user_id_1
+    assert users.next().object_id == user_id_2
     mock_box_session.get.assert_called_once_with('{0}/users'.format(API.BASE_API_URL), params=expected_params)
-    assert users[0].object_id == user_id_1
-    assert users[1].object_id == user_id_2
 
 
 def test_search_instantiates_search_and_calls_search(
@@ -239,8 +256,8 @@ def test_search_instantiates_search_and_calls_search(
         ancestor_folders=[Folder(mock_box_session, folder_id)],
         file_extensions=['.jpg'],
     )
-    assert search_result[0].object_id == file_id
-    assert search_result[1].object_id == folder_id
+    assert search_result.next().object_id == file_id
+    assert search_result.next().object_id == folder_id
 
 
 def test_events_returns_event_object(mock_client):
diff --git a/test/unit/object/test_folder.py b/test/unit/object/test_folder.py
index f17f695..98f357d 100644
--- a/test/unit/object/test_folder.py
+++ b/test/unit/object/test_folder.py
@@ -13,8 +13,6 @@
 from boxsdk.object.file import File
 from boxsdk.object.collaboration import Collaboration, CollaborationRole
 from boxsdk.object.folder import Folder, FolderSyncState
-from boxsdk.pagination.limit_offset_based_object_collection import LimitOffsetBasedObjectCollection
-from boxsdk.pagination.marker_based_object_collection import MarkerBasedObjectCollection
 from boxsdk.session.box_response import BoxResponse
 
 
@@ -56,10 +54,16 @@ def mock_items_response(mock_items):
     # pylint:disable=redefined-outer-name
     def get_response(limit, offset):
         items_json, items = mock_items
+        entries = items_json[offset:limit + offset]
         mock_box_response = Mock(BoxResponse)
         mock_network_response = Mock(DefaultNetworkResponse)
         mock_box_response.network_response = mock_network_response
-        mock_box_response.json.return_value = mock_json = {'entries': items_json[offset:limit + offset]}
+        mock_box_response.json.return_value = mock_json = {
+            'entries': entries,
+            'total_count': len(entries),
+            'limit': limit,
+            'offset': offset,
+        }
         mock_box_response.content = json.dumps(mock_json).encode()
         mock_box_response.status_code = 200
         mock_box_response.ok = True
@@ -122,25 +126,16 @@ def test_get_items(test_folder, mock_box_session, mock_items_response, limit, of
     # pylint:disable=redefined-outer-name
     expected_url = test_folder.get_url('items')
     mock_box_session.get.return_value, expected_items = mock_items_response(limit, offset)
-    items = test_folder.get_items(limit, offset, fields)
+    items = test_folder.get_items(limit, offset, fields=fields)
     expected_params = {'limit': limit, 'offset': offset}
     if fields:
         expected_params['fields'] = ','.join(fields)
+    for actual, expected in zip(items, expected_items):
+        assert actual == expected
     mock_box_session.get.assert_called_once_with(expected_url, params=expected_params)
-    assert items == expected_items
     assert all([i.id == e.object_id for i, e in zip(items, expected_items)])
 
 
-def test_get_items_marker_returns_marker_instance(test_folder):
-    limit_offset_object_collection = test_folder.get_items_limit_offset()
-    assert isinstance(limit_offset_object_collection, LimitOffsetBasedObjectCollection)
-
-
-def test_get_items_limit_offset_returns_limit_offset_instance(test_folder):
-    marker_object_collection = test_folder.get_items_marker()
-    assert isinstance(marker_object_collection, MarkerBasedObjectCollection)
-
-
 @pytest.mark.parametrize('is_stream', (True, False))
 def test_upload(
         test_folder,
diff --git a/test/unit/object/test_group.py b/test/unit/object/test_group.py
index a6caa36..84d8f61 100644
--- a/test/unit/object/test_group.py
+++ b/test/unit/object/test_group.py
@@ -116,17 +116,20 @@ def membership_responses(total, page_size, hidden_in_batch=None):
     (5, 4),
     (9, 4),
 ])
-def test_membership(test_group, mock_box_session, mock_membership_responses, total, page_size):
+def test_get_memberships(test_group, mock_box_session, mock_membership_responses, total, page_size):
     # pylint:disable=redefined-outer-name
     # Each call the 'get' (the GET next page call) will return the next response
     mock_box_session.get.side_effect = mock_membership_responses(total, page_size)
 
     # Get all the members
-    all_members = list(test_group.membership(0, page_size))
+    all_members = test_group.get_memberships()
 
     # Assert we got the expected number of membership instances
-    assert len(all_members) == total
-    assert all(isinstance(m, GroupMembership) for m in all_members)
+    count = 0
+    for membership in all_members:
+        count += 1
+        assert isinstance(membership, GroupMembership)
+    assert count == total
 
 
 @pytest.mark.parametrize('hidden_in_batch', [
@@ -135,7 +138,7 @@ def test_membership(test_group, mock_box_session, mock_membership_responses, tot
     (10, 10, 9),
     (10, 10, 10),
 ])
-def test_membership_with_hidden_results(test_group, mock_box_session, mock_membership_responses, hidden_in_batch):
+def test_get_memberships_with_hidden_results(test_group, mock_box_session, mock_membership_responses, hidden_in_batch):
     """
     This test verifies that the SDK properly deals with missing (aka hidden) data potentially present in a paged
     API. The API might indicate that the total_size is X, but in actuality the pages API could return less than X
@@ -151,47 +154,11 @@ def test_membership_with_hidden_results(test_group, mock_box_session, mock_membe
     mock_box_session.get.side_effect = mock_membership_responses(total, page_size, hidden_in_batch=hidden_in_batch)
 
     # Get all the members
-    all_members = list(test_group.membership(0, page_size))
+    all_members = test_group.get_memberships(0, page_size)
 
     # Assert we got the expected number of membership instances
-    assert len(all_members) == total - total_hidden
-    assert all(isinstance(m, GroupMembership) for m in all_members)
-
-
-def test_membership_with_page_info(test_group, mock_box_session, mock_membership_responses):
-    """
-    Verify that the paging info returned by the membership call when include_page_info=True
-    is correct, thus allowing a client complete knowledge of when another API call
-    is going to be triggered.
-    """
-    # pylint:disable=redefined-outer-name
-    total = 9
-    page_size = 3
-    hidden_in_batch = 0, 2, 1
-
-    # Each call to 'get' (the GET next page call) will return the next response
-    mock_box_session.get.side_effect = mock_membership_responses(total, page_size, hidden_in_batch=hidden_in_batch)
-
-    # Initialize the generator of all the membership
-    group_generator = test_group.membership(0, page_size, include_page_info=True)
-
-    # manually get all the items, verifying that the page-info data is correct.
-    _, page_size, index = next(group_generator)
-    assert page_size == 3 and index == 0
-    _, page_size, index = next(group_generator)
-    assert page_size == 3 and index == 1
-    _, page_size, index = next(group_generator)
-    assert page_size == 3 and index == 2
-
-    # This next call will trigger a new GET request, returning the 2nd page
-    _, page_size, index = next(group_generator)
-    assert page_size == 1 and index == 0
-
-    # This next call will trigger a new GET request, returning the 3rd page
-    _, page_size, index = next(group_generator)
-    assert page_size == 2 and index == 0
-    _, page_size, index = next(group_generator)
-    assert page_size == 2 and index == 1
-
-    with pytest.raises(StopIteration):
-        next(group_generator)
+    count = 0
+    for membership in all_members:
+        count += 1
+        assert isinstance(membership, GroupMembership)
+    assert count == total - total_hidden
diff --git a/test/unit/object/test_search.py b/test/unit/object/test_search.py
index 0047db8..d823db4 100644
--- a/test/unit/object/test_search.py
+++ b/test/unit/object/test_search.py
@@ -68,9 +68,10 @@ def search_entries():
 
 @pytest.fixture
 def search_response():
+    entries = search_entries()
     return {
-        'entries': search_entries(),
-        'total_count': 0,
+        'entries': entries,
+        'total_count': len(entries),
         'limit': 20,
         'offset': 0
     }
@@ -125,7 +126,8 @@ def test_search_with_value_based_filters(
         result_type=search_result_type,
         content_types=search_content_types,
     )
-    assert response == [File(mock_box_session, search_entry['id'], search_entry) for search_entry in search_entries]
+    for actual, expected in zip(response, [File(mock_box_session, item['id'], item) for item in search_entries]):
+        assert actual == expected
 
     mock_box_session.get.assert_called_once_with(
         test_search.get_url(),
@@ -163,7 +165,8 @@ def test_search_with_range_filters(
         result_type=search_result_type,
         content_types=search_content_types,
     )
-    assert response == [File(mock_box_session, search_entry['id'], search_entry) for search_entry in search_entries]
+    for actual, expected in zip(response, [File(mock_box_session, item['id'], item) for item in search_entries]):
+        assert actual == expected
 
     mock_box_session.get.assert_called_once_with(
         test_search.get_url(),