Repository: plone/plone.restapi (language: python)

Issue:
Calls to `@history` fail when full history is empty.
Calls to the `@history` endpoint might wail with `TypeError: 'NoneType' object is not iterable` under certain circumstances.

- We rely on the history viewlet https://github.com/plone/plone.restapi/blob/d78a8411d1166ee785ee71292a540f39fc2cf44c/src/plone/restapi/services/history/get.py#L42 and iterate over its result https://github.com/plone/plone.restapi/blob/d78a8411d1166ee785ee71292a540f39fc2cf44c/src/plone/restapi/services/history/get.py#L54 
- however full history returns `None` when the history can't be retrieved or is empty https://github.com/plone/plone.app.layout/blob/0ba390844d7c4f7dc88fe1688e80876eea58be66/plone/app/layout/viewlets/content.py#L481 
- which is the case when there are no versions and a user can’t access review history, for which user needs `Review … ` permissions https://github.com/plone/plone.app.layout/blob/0ba390844d7c4f7dc88fe1688e80876eea58be66/plone/app/layout/viewlets/content.py#L344-L347 


Discussion / hints:

Fix patch:
diff --git a/news/1113.bugfix b/news/1113.bugfix
new file mode 100644
index 0000000000..9e0342db8e
--- /dev/null
+++ b/news/1113.bugfix
@@ -0,0 +1,2 @@
+Fix ``@history`` when full history is empty.
+[deiferni]
diff --git a/src/plone/restapi/services/history/get.py b/src/plone/restapi/services/history/get.py
index 95f60c99d3..247f9ea4de 100644
--- a/src/plone/restapi/services/history/get.py
+++ b/src/plone/restapi/services/history/get.py
@@ -40,6 +40,8 @@ def reply(self):
         content_history_viewlet.navigation_root_url = site_url
         content_history_viewlet.site_url = site_url
         history = content_history_viewlet.fullHistory()
+        if history is None:
+            history = []
 
         unwanted_keys = [
             "diff_current_url",


Test patch:
diff --git a/src/plone/restapi/tests/test_services_history.py b/src/plone/restapi/tests/test_services_history.py
index a3ade88c90..325ba04c5e 100644
--- a/src/plone/restapi/tests/test_services_history.py
+++ b/src/plone/restapi/tests/test_services_history.py
@@ -1,9 +1,12 @@
 # -*- coding: utf-8 -*-
 from plone import api
+from plone.app.testing import login
 from plone.app.testing import setRoles
 from plone.app.testing import SITE_OWNER_NAME
 from plone.app.testing import SITE_OWNER_PASSWORD
 from plone.app.testing import TEST_USER_ID
+from plone.app.testing import TEST_USER_NAME
+from plone.app.testing import TEST_USER_PASSWORD
 from plone.restapi.testing import PLONE_RESTAPI_DX_FUNCTIONAL_TESTING
 from plone.restapi.testing import RelativeSession
 
@@ -124,6 +127,52 @@ def test_no_sharing(self):
         self.assertNotIn("sharing", response.json())
 
 
+class TestHistoryEndpointEmptyOrInacessibleHistory(unittest.TestCase):
+
+    layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING
+
+    def _disable_auto_versioning(self, content_type):
+        portal_repository = self.portal.portal_repository
+        types = list(portal_repository.getVersionableContentTypes())
+        types.remove(content_type)
+        portal_repository.setVersionableContentTypes(types)
+        portal_repository.removePolicyFromContentType(
+            content_type, u"version_on_revert"
+        )
+
+    def setUp(self):
+        self.portal = self.layer["portal"]
+        self.portal_url = self.portal.absolute_url()
+
+        login(self.portal, SITE_OWNER_NAME)
+
+        # disabling auto versioning is necessary to have an empty revision
+        # history
+        self._disable_auto_versioning("Document")
+
+        self.portal.invokeFactory(
+            "Document", id="doc_with_empty_history", title="My Document"
+        )
+        self.doc = self.portal.doc_with_empty_history
+        api.content.transition(self.doc, "publish")
+        self.endpoint_url = "{}/@history".format(self.doc.absolute_url())
+
+        self.api_session = RelativeSession(self.portal_url)
+        self.api_session.headers.update({"Accept": "application/json"})
+        self.api_session.auth = (TEST_USER_NAME, TEST_USER_PASSWORD)
+        # forbid access to `workflowHistory`
+        setRoles(self.portal, TEST_USER_ID, ["Reader", "Contributor"])
+        transaction.commit()
+
+    def tearDown(self):
+        self.api_session.close()
+
+    def test_empty_or_inaccessible_full_history_returns_empty_list(self):
+        url = self.doc.absolute_url() + "/@history"
+        response = self.api_session.get(url)
+        self.assertEqual([], response.json())
+
+
 class TestHistoryEndpointTranslatedMessages(unittest.TestCase):
     layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING