[Testbot] Plone 5.0 - Python 2.7 - Build # 2478 - Regression! - 2 failure(s)

jenkins at plone.org jenkins at plone.org
Mon May 12 21:39:57 UTC 2014


-------------------------------------------------------------------------------
Plone 5.0 - Python 2.7 - Build # 2478 - Failure!
-------------------------------------------------------------------------------

http://jenkins.plone.org/job/plone-5.0-python-2.7/2478/


-------------------------------------------------------------------------------
CHANGES
-------------------------------------------------------------------------------

Repository: plone.app.discussion
Branch: refs/heads/master
Date: 2013-11-21T17:22:12Z
Author: Kees Hink (khink) <keeshink at gmail.com>
Commit: https://github.com/plone/plone.app.discussion/commit/5fb6968fcad4e40c25f6ae65a331f015bbd30ce0

Add a test for Acquisition in comments.

Files changed:
A plone/app/discussion/tests/configure.zcml
A plone/app/discussion/tests/profile/types.xml
A plone/app/discussion/tests/profile/types/sample_content_type.xml
A plone/app/discussion/tests/profile/workflows.xml
A plone/app/discussion/tests/profile/workflows/comment_workflow_acquired_view/definition.xml
A plone/app/discussion/tests/test_acquisition.py
M plone/app/discussion/testing.py

diff --git a/plone/app/discussion/testing.py b/plone/app/discussion/testing.py
index cf1066c..02b8fa3 100644
--- a/plone/app/discussion/testing.py
+++ b/plone/app/discussion/testing.py
@@ -35,10 +35,14 @@ def setUpZope(self, app, configurationContext):
         xmlconfig.file('configure.zcml',
                        plone.app.discussion,
                        context=configurationContext)
+        xmlconfig.file('configure.zcml',
+                       plone.app.discussion.tests,
+                       context=configurationContext)
 
     def setUpPloneSite(self, portal):
         # Install into Plone site using portal_setup
         applyProfile(portal, 'plone.app.discussion:default')
+        applyProfile(portal, 'plone.app.discussion.tests:testing')
 
         # Creates some users
         acl_users = getToolByName(portal, 'acl_users')
diff --git a/plone/app/discussion/tests/configure.zcml b/plone/app/discussion/tests/configure.zcml
new file mode 100644
index 0000000..ee79b95
--- /dev/null
+++ b/plone/app/discussion/tests/configure.zcml
@@ -0,0 +1,16 @@
+<configure
+  xmlns="http://namespaces.zope.org/zope"
+  xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
+  i18n_domain="freitag.discussion">
+
+  <include package="plone.app.dexterity" />
+
+  <genericsetup:registerProfile
+    name="testing"
+    title="plone.app.discussion.testing"
+    directory="profile"
+    description="Testing profile for plone.app.discussion"
+    provides="Products.GenericSetup.interfaces.EXTENSION"
+    />
+
+</configure>
diff --git a/plone/app/discussion/tests/profile/types.xml b/plone/app/discussion/tests/profile/types.xml
new file mode 100644
index 0000000..0aec569
--- /dev/null
+++ b/plone/app/discussion/tests/profile/types.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<object name="portal_types" meta_type="Plone Types Tool">
+  <object name="sample_content_type" meta_type="Dexterity FTI" />
+</object>
diff --git a/plone/app/discussion/tests/profile/types/sample_content_type.xml b/plone/app/discussion/tests/profile/types/sample_content_type.xml
new file mode 100644
index 0000000..7869638
--- /dev/null
+++ b/plone/app/discussion/tests/profile/types/sample_content_type.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<object name="sample_content_type"
+   meta_type="Dexterity FTI"
+   i18n:domain="freitag.discussion" xmlns:i18n="http://xml.zope.org/namespaces/i18n">
+
+  <!-- Basic metadata -->
+  <property name="title" i18n:translate="">sample_content_type</property>
+  <property name="description"
+    i18n:translate="">Sample Content</property>
+  <property name="content_icon">document_icon.png</property>
+  <property name="global_allow">True</property>
+  <property name="filter_content_types">True</property>
+  <property name="allowed_content_types">
+  </property>
+  <property name="allow_discussion">True</property>
+
+  <property name="klass">plone.dexterity.content.Item</property>
+
+  <property name="add_permission">cmf.AddPortalContent</property>
+  <property name="behaviors">
+    <element value="plone.app.content.interfaces.INameFromTitle" />
+  </property>
+
+  <!-- View information -->
+  <property name="default_view">view</property>
+  <property name="default_view_fallback">False</property>
+  <property name="view_methods">
+    <element value="view" />
+  </property>
+
+  <!-- Method aliases -->
+  <alias from="(Default)" to="(selected layout)" />
+  <alias from="edit" to="@@edit" />
+  <alias from="sharing" to="@@sharing" />
+  <alias from="view" to="@@view" />
+
+  <!-- Actions -->
+  <action title="View" action_id="view" category="object" condition_expr=""
+    url_expr="string:${object_url}/" visible="True">
+    <permission value="View" />
+  </action>
+
+  <action title="Edit" action_id="edit" category="object" condition_expr=""
+    url_expr="string:${object_url}/edit" visible="True">
+    <permission value="Modify portal content" />
+  </action>
+</object>
diff --git a/plone/app/discussion/tests/profile/workflows.xml b/plone/app/discussion/tests/profile/workflows.xml
new file mode 100644
index 0000000..500f444
--- /dev/null
+++ b/plone/app/discussion/tests/profile/workflows.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<object name="portal_workflow" meta_type="CMF Workflow Tool">
+ <object name="comment_workflow_acquired_view" meta_type="Workflow"/>
+</object>
diff --git a/plone/app/discussion/tests/profile/workflows/comment_workflow_acquired_view/definition.xml b/plone/app/discussion/tests/profile/workflows/comment_workflow_acquired_view/definition.xml
new file mode 100644
index 0000000..89a9fb2
--- /dev/null
+++ b/plone/app/discussion/tests/profile/workflows/comment_workflow_acquired_view/definition.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+<dc-workflow
+    workflow_id="comment_workflow_acquired_view"
+    title="Single State Workflow"
+    description="- Essentially a workflow with no transitions, but has a Published state, so portlets and applications that expect that state will continue to work."
+    state_variable="review_state"
+    initial_state="published"
+    manager_bypass="False">
+ <permission>Access contents information</permission>
+ <permission>Change portal events</permission>
+ <permission>Modify portal content</permission>
+ <permission>View</permission>
+ <state state_id="published" title="Published">
+  <description>Visible to everyone, editable by the owner.</description>
+  <permission-map name="Access contents information" acquired="False">
+   <permission-role>Anonymous</permission-role>
+  </permission-map>
+  <permission-map name="Change portal events" acquired="False">
+   <permission-role>Editor</permission-role>
+   <permission-role>Manager</permission-role>
+   <permission-role>Owner</permission-role>
+   <permission-role>Site Administrator</permission-role>
+  </permission-map>
+  <permission-map name="Modify portal content" acquired="False">
+   <permission-role>Editor</permission-role>
+   <permission-role>Manager</permission-role>
+   <permission-role>Owner</permission-role>
+   <permission-role>Site Administrator</permission-role>
+  </permission-map>
+  <permission-map name="View" acquired="True">
+  </permission-map>
+ </state>
+ <variable variable_id="action" for_catalog="False" for_status="True" update_always="True">
+  <description>Previous transition</description>
+  <default>
+   <expression>transition/getId|nothing</expression>
+  </default>
+  <guard>
+  </guard>
+ </variable>
+ <variable variable_id="actor" for_catalog="False" for_status="True" update_always="True">
+  <description>The ID of the user who performed the previous transition</description>
+  <default>
+   <expression>user/getId</expression>
+  </default>
+  <guard>
+  </guard>
+ </variable>
+ <variable variable_id="comments" for_catalog="False" for_status="True" update_always="True">
+  <description>Comment about the last transition</description>
+  <default>
+   <expression>python:state_change.kwargs.get('comment', '')</expression>
+  </default>
+  <guard>
+  </guard>
+ </variable>
+ <variable variable_id="review_history" for_catalog="False" for_status="False" update_always="False">
+  <description>Provides access to workflow history</description>
+  <default>
+   <expression>state_change/getHistory</expression>
+  </default>
+  <guard>
+   <guard-permission>Request review</guard-permission>
+   <guard-permission>Review portal content</guard-permission>
+  </guard>
+ </variable>
+ <variable variable_id="time" for_catalog="False" for_status="True" update_always="True">
+  <description>When the previous transition was performed</description>
+  <default>
+   <expression>state_change/getDateTime</expression>
+  </default>
+  <guard>
+  </guard>
+ </variable>
+</dc-workflow>
diff --git a/plone/app/discussion/tests/test_acquisition.py b/plone/app/discussion/tests/test_acquisition.py
new file mode 100644
index 0000000..31d5f81
--- /dev/null
+++ b/plone/app/discussion/tests/test_acquisition.py
@@ -0,0 +1,145 @@
+# -*- coding: utf-8 -*-
+from Acquisition import aq_chain
+from plone.app.discussion.testing import \
+    PLONE_APP_DISCUSSION_INTEGRATION_TESTING
+from plone.app.discussion.interfaces import IConversation
+from plone.app.testing import TEST_USER_ID, setRoles
+from Products.CMFCore.utils import getToolByName
+from zope.component import createObject
+
+import unittest2 as unittest
+
+
+dexterity_type_name = 'sample_content_type'
+dexterity_object_id = 'instance-of-dexterity-type'
+archetypes_object_id = 'instance-of-archetypes-type'
+one_state_workflow = 'one_state_workflow'
+comment_workflow_acquired_view = 'comment_workflow_acquired_view'
+
+
+def _anonymousCanView(obj):
+    """Use rolesOfPermission() to sees if Anonymous has View permission on an
+    object"""
+    roles_of_view_permission = obj.rolesOfPermission("View")
+    # rolesOfPermission returns a list of dictionaries that have the key
+    # 'name' for role.
+    anon_views = [r for r in roles_of_view_permission
+                  if r['name'] == 'Anonymous']
+    # only one entry per role should be present
+    anon_view = anon_views[0]
+    # if this role has the permission, 'selected' is set to 'SELECTED'
+    return anon_view['selected'] == 'SELECTED'
+
+
+class DexterityAcquisitionTest(unittest.TestCase):
+    """See test_view_permission."""
+
+    layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
+
+    def setUp(self):
+        self.portal = self.layer['portal']
+        self.request = self.layer['request']
+        setRoles(self.portal, TEST_USER_ID, ['Manager'])
+        self.wftool = getToolByName(self.portal, 'portal_workflow')
+
+        # Use customized workflow for comments.
+        self.wftool.setChainForPortalTypes(
+            ['Discussion Item'],
+            (comment_workflow_acquired_view,),
+        )
+
+        # Use one_state_workflow for Document and sample_content_type,
+        # so they're always published.
+        self.wftool.setChainForPortalTypes(
+            ['Document', dexterity_type_name],
+            (one_state_workflow,),
+        )
+
+        # Create a dexterity item and add a comment.
+        self.portal.invokeFactory(
+            id=dexterity_object_id,
+            title='Instance Of Dexterity Type',
+            type_name=dexterity_type_name,
+        )
+        self.dexterity_object = self.portal.get(dexterity_object_id)
+        dx_conversation = IConversation(self.dexterity_object)
+        self.dexterity_conversation = dx_conversation
+        comment1 = createObject('plone.Comment')
+        dx_conversation.addComment(comment1)
+        self.dexterity_comment = comment1
+
+        # Create an Archetypes item and add a comment.
+        self.portal.invokeFactory(
+            id=archetypes_object_id,
+            title='Instance Of Archetypes Type',
+            type_name='Document',
+        )
+        self.archetypes_object = self.portal.get(archetypes_object_id)
+        at_conversation = IConversation(self.archetypes_object)
+        self.archetypes_conversation = at_conversation
+        comment2 = createObject('plone.Comment')
+        at_conversation.addComment(comment2)
+        self.archetypes_comment = comment2
+
+    def test_workflows_installed(self):
+        """Check that the new comment workflow has been installed properly.
+        (Just a test to check our test setup.)
+        """
+        workflows = self.wftool.objectIds()
+        self.assertTrue('comment_workflow_acquired_view' in workflows)
+
+    def test_workflows_applied(self):
+        """Check that all objects have the workflow that we expect.
+        (Just a test to check our test setup.)"""
+        self.assertEqual(
+            self.wftool.getChainFor(self.archetypes_object),
+            (one_state_workflow,)
+        )
+        self.assertEqual(
+            self.wftool.getChainFor(self.dexterity_object),
+            (one_state_workflow,)
+        )
+        self.assertEqual(
+            self.wftool.getChainFor(self.archetypes_comment),
+            (comment_workflow_acquired_view,)
+        )
+        self.assertEqual(
+            self.wftool.getChainFor(self.dexterity_comment),
+            (comment_workflow_acquired_view,)
+        )
+
+    def test_view_permission(self):
+        """Test that if the View permission on Discussion Items is acquired,
+        Anonymous can view comments on published items."""
+
+        # Anonymous has View permission on commented objects.
+        self.assertTrue(_anonymousCanView(self.archetypes_object))
+        self.assertTrue(_anonymousCanView(self.dexterity_object))
+
+        # Fails: Anonymous should therefore have View permission on the
+        # comments.
+        self.assertTrue(_anonymousCanView(self.archetypes_comment))
+        self.assertTrue(_anonymousCanView(self.dexterity_comment))
+
+    def test_acquisition_chain(self):
+        """The acquisition chain for the comment should contain the same items
+        as that of the conversation.
+
+        Note that the list returned by aq_inner has the innermost object
+        first."""
+
+        # Fails: list index out of range
+        at_comment_chain = aq_chain(self.archetypes_comment)
+        at_conversation_chain = aq_chain(self.archetypes_conversation)
+        for (index, item) in enumerate(at_conversation_chain):
+            self.assertEqual(item, at_comment_chain[index + 1])
+
+        # Fails: list index out of range
+        dx_comment_chain = aq_chain(self.dexterity_comment)
+        dx_conversation_chain = aq_chain(self.dexterity_conversation)
+        for (index, item) in enumerate(dx_conversation_chain):
+            self.assertEqual(item, dx_comment_chain[index + 1])
+
+
+def test_suite():
+    return unittest.defaultTestLoader.loadTestsFromName(__name__)


Repository: plone.app.discussion
Branch: refs/heads/master
Date: 2013-11-21T17:23:36Z
Author: vmaksymiv (vmaksymiv) <vmaksymiv at quintagroup.com>
Commit: https://github.com/plone/plone.app.discussion/commit/8e56b99638f48170725a29425312ae6f3966aec8

added test acquisition chain for unwrapped object

Conflicts:
	plone/app/discussion/tests/test_acquisition.py

Files changed:
M plone/app/discussion/tests/test_acquisition.py

diff --git a/plone/app/discussion/tests/test_acquisition.py b/plone/app/discussion/tests/test_acquisition.py
index 31d5f81..4f4d01b 100644
--- a/plone/app/discussion/tests/test_acquisition.py
+++ b/plone/app/discussion/tests/test_acquisition.py
@@ -1,5 +1,8 @@
 # -*- coding: utf-8 -*-
-from Acquisition import aq_chain
+from AccessControl.User import User  # before SpecialUsers
+from AccessControl.SpecialUsers import nobody as user_nobody
+from AccessControl.PermissionRole import rolesForPermissionOn
+from Acquisition import aq_chain, aq_base
 from plone.app.discussion.testing import \
     PLONE_APP_DISCUSSION_INTEGRATION_TESTING
 from plone.app.discussion.interfaces import IConversation
@@ -140,6 +143,25 @@ def test_acquisition_chain(self):
         for (index, item) in enumerate(dx_conversation_chain):
             self.assertEqual(item, dx_comment_chain[index + 1])
 
+    def test_acquisition_base_object_chain(self):
+        """ The acquisition chain for the object without wrappers should return
+            list which contains only the object.
+        """
+
+        at_object_base_chain = aq_chain(aq_base(self.archetypes_object))
+        dx_object_base_chain = aq_chain(aq_base(self.dexterity_object))
+
+        # Fails: acquisition chain has more than one object
+        self.assertTrue(len(at_object_base_chain) == 1)
+        self.assertTrue(len(dx_object_base_chain) == 1)
+
+        at_comment_base_chain = aq_chain(aq_base(self.archetypes_comment))
+        dx_comment_base_chain = aq_chain(aq_base(self.dexterity_comment))
+
+        # Fails: acquisition chain has more than one object
+        self.assertTrue(len(at_comment_base_chain) == 1)
+        self.assertTrue(len(dx_comment_base_chain) == 1)
+
 
 def test_suite():
     return unittest.defaultTestLoader.loadTestsFromName(__name__)


Repository: plone.app.discussion
Branch: refs/heads/master
Date: 2013-11-21T18:13:31Z
Author: Alan Hoey (evilbungle) <alan.hoey at gmail.com>
Commit: https://github.com/plone/plone.app.discussion/commit/4be4c03c49f478e7339d59b008fc21ba52576e9e

Fix total comments index

Files changed:
M plone/app/discussion/conversation.py

diff --git a/plone/app/discussion/conversation.py b/plone/app/discussion/conversation.py
index ba27c0f..16a3cec 100644
--- a/plone/app/discussion/conversation.py
+++ b/plone/app/discussion/conversation.py
@@ -50,8 +50,14 @@
 
 from AccessControl.SpecialUsers import nobody as user_nobody
 
+from ComputedAttribute import ComputedAttribute
+
 ANNOTATION_KEY = 'plone.app.discussion:conversation'
 
+def computed_attribute_decorator(level=0):
+    def computed_attribute_wrapper(func):
+        return ComputedAttribute(func, level)
+    return computed_attribute_wrapper
 
 class Conversation(Traversable, Persistent, Explicit):
     """A conversation is a container for all comments on a content object.
@@ -87,10 +93,10 @@ def enabled(self):
         parent = aq_inner(self.__parent__)
         return parent.restrictedTraverse('@@conversation_view').enabled()
 
-    @property
+    @computed_attribute_decorator(level=1)
     def total_comments(self):
         public_comments = [
-            x for x in self._comments.values()
+            x for x in self.values()
             if user_nobody.has_permission('View', x)
         ]
         return len(public_comments)


Repository: plone.app.discussion
Branch: refs/heads/master
Date: 2013-11-22T11:51:47Z
Author: Jess Henderson (jessnorwood) <jess at delib.net>
Commit: https://github.com/plone/plone.app.discussion/commit/7269dfca6568e9d5c8554aaac5228c957efc1c9a

Add dev.delib-da.1 to version pin

Files changed:
M setup.py

diff --git a/setup.py b/setup.py
index af74549..7a91fc2 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
 from setuptools import setup, find_packages
 
-version = '2.3.0dev'
+version = '2.3.0dev.delib-da.1'
 
 install_requires = [
     'setuptools',


Repository: plone.app.discussion
Branch: refs/heads/master
Date: 2013-11-25T12:06:05Z
Author: Alan Hoey (evilbungle) <alan.hoey at gmail.com>
Commit: https://github.com/plone/plone.app.discussion/commit/4804868ab25a5c5c38f73fed25065ab6c849e6c3

Refactor acquisition tests to define the expected behaviour when dealing with wrapped and unwrapped comments.

Files changed:
M plone/app/discussion/tests/test_acquisition.py

diff --git a/plone/app/discussion/tests/test_acquisition.py b/plone/app/discussion/tests/test_acquisition.py
index 4f4d01b..72d68fe 100644
--- a/plone/app/discussion/tests/test_acquisition.py
+++ b/plone/app/discussion/tests/test_acquisition.py
@@ -2,7 +2,7 @@
 from AccessControl.User import User  # before SpecialUsers
 from AccessControl.SpecialUsers import nobody as user_nobody
 from AccessControl.PermissionRole import rolesForPermissionOn
-from Acquisition import aq_chain, aq_base
+from Acquisition import aq_chain
 from plone.app.discussion.testing import \
     PLONE_APP_DISCUSSION_INTEGRATION_TESTING
 from plone.app.discussion.interfaces import IConversation
@@ -19,23 +19,8 @@
 one_state_workflow = 'one_state_workflow'
 comment_workflow_acquired_view = 'comment_workflow_acquired_view'
 
-
-def _anonymousCanView(obj):
-    """Use rolesOfPermission() to sees if Anonymous has View permission on an
-    object"""
-    roles_of_view_permission = obj.rolesOfPermission("View")
-    # rolesOfPermission returns a list of dictionaries that have the key
-    # 'name' for role.
-    anon_views = [r for r in roles_of_view_permission
-                  if r['name'] == 'Anonymous']
-    # only one entry per role should be present
-    anon_view = anon_views[0]
-    # if this role has the permission, 'selected' is set to 'SELECTED'
-    return anon_view['selected'] == 'SELECTED'
-
-
-class DexterityAcquisitionTest(unittest.TestCase):
-    """See test_view_permission."""
+class AcquisitionTest(unittest.TestCase):
+    """ Define the expected behaviour of wrapped and unwrapped comments. """
 
     layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
 
@@ -64,12 +49,14 @@ def setUp(self):
             title='Instance Of Dexterity Type',
             type_name=dexterity_type_name,
         )
+        
         self.dexterity_object = self.portal.get(dexterity_object_id)
         dx_conversation = IConversation(self.dexterity_object)
         self.dexterity_conversation = dx_conversation
-        comment1 = createObject('plone.Comment')
-        dx_conversation.addComment(comment1)
-        self.dexterity_comment = comment1
+        dx_comment = createObject('plone.Comment')
+        dx_conversation.addComment(dx_comment)
+        self.unwrapped_dexterity_comment = dx_comment
+        self.wrapped_dexterity_comment = dx_conversation[dx_comment.id]
 
         # Create an Archetypes item and add a comment.
         self.portal.invokeFactory(
@@ -77,12 +64,15 @@ def setUp(self):
             title='Instance Of Archetypes Type',
             type_name='Document',
         )
+        
         self.archetypes_object = self.portal.get(archetypes_object_id)
         at_conversation = IConversation(self.archetypes_object)
         self.archetypes_conversation = at_conversation
-        comment2 = createObject('plone.Comment')
-        at_conversation.addComment(comment2)
-        self.archetypes_comment = comment2
+        at_comment = createObject('plone.Comment')
+        at_conversation.addComment(at_comment)
+        self.unwrapped_archetypes_comment = at_comment
+        self.wrapped_archetypes_comment = at_conversation[at_comment.id]
+        
 
     def test_workflows_installed(self):
         """Check that the new comment workflow has been installed properly.
@@ -103,64 +93,85 @@ def test_workflows_applied(self):
             (one_state_workflow,)
         )
         self.assertEqual(
-            self.wftool.getChainFor(self.archetypes_comment),
+            self.wftool.getChainFor(self.unwrapped_archetypes_comment),
             (comment_workflow_acquired_view,)
         )
         self.assertEqual(
-            self.wftool.getChainFor(self.dexterity_comment),
+            self.wftool.getChainFor(self.unwrapped_dexterity_comment),
             (comment_workflow_acquired_view,)
         )
 
-    def test_view_permission(self):
-        """Test that if the View permission on Discussion Items is acquired,
-        Anonymous can view comments on published items."""
-
-        # Anonymous has View permission on commented objects.
-        self.assertTrue(_anonymousCanView(self.archetypes_object))
-        self.assertTrue(_anonymousCanView(self.dexterity_object))
-
-        # Fails: Anonymous should therefore have View permission on the
-        # comments.
-        self.assertTrue(_anonymousCanView(self.archetypes_comment))
-        self.assertTrue(_anonymousCanView(self.dexterity_comment))
-
-    def test_acquisition_chain(self):
-        """The acquisition chain for the comment should contain the same items
-        as that of the conversation.
-
-        Note that the list returned by aq_inner has the innermost object
-        first."""
-
-        # Fails: list index out of range
-        at_comment_chain = aq_chain(self.archetypes_comment)
-        at_conversation_chain = aq_chain(self.archetypes_conversation)
-        for (index, item) in enumerate(at_conversation_chain):
-            self.assertEqual(item, at_comment_chain[index + 1])
-
-        # Fails: list index out of range
-        dx_comment_chain = aq_chain(self.dexterity_comment)
-        dx_conversation_chain = aq_chain(self.dexterity_conversation)
-        for (index, item) in enumerate(dx_conversation_chain):
-            self.assertEqual(item, dx_comment_chain[index + 1])
-
-    def test_acquisition_base_object_chain(self):
-        """ The acquisition chain for the object without wrappers should return
-            list which contains only the object.
-        """
-
-        at_object_base_chain = aq_chain(aq_base(self.archetypes_object))
-        dx_object_base_chain = aq_chain(aq_base(self.dexterity_object))
-
-        # Fails: acquisition chain has more than one object
-        self.assertTrue(len(at_object_base_chain) == 1)
-        self.assertTrue(len(dx_object_base_chain) == 1)
-
-        at_comment_base_chain = aq_chain(aq_base(self.archetypes_comment))
-        dx_comment_base_chain = aq_chain(aq_base(self.dexterity_comment))
-
-        # Fails: acquisition chain has more than one object
-        self.assertTrue(len(at_comment_base_chain) == 1)
-        self.assertTrue(len(dx_comment_base_chain) == 1)
+    def test_comment_acquisition_chain(self):
+        """ Test that the acquisition chains for wrapped and unwrapped
+            comments are as expected. """
+        
+        # Unwrapped comments rely on __parent__ attributes to determine 
+        # parentage.  Frustratingly there is no guarantee that __parent__
+        # is always set, so the computed acquisition chain may be short.
+        # In this case the unwrapped AT and DX objects stored as the 
+        # conversation parents don't have a __parent__, preventing the portal
+        # from being included in the chain.
+        self.assertNotIn(self.portal,
+                         aq_chain(self.unwrapped_archetypes_comment))
+        self.assertNotIn(self.portal,
+                         aq_chain(self.unwrapped_dexterity_comment))
+
+        # Wrapped comments however have a complete chain and thus can find the
+        # portal object reliably.
+        self.assertIn(self.portal,aq_chain(self.wrapped_archetypes_comment))
+        self.assertIn(self.portal,aq_chain(self.wrapped_dexterity_comment))
+
+    
+    def test_acquiring_comment_permissions(self):
+        """ Unwrapped comments should not be able to acquire permissions
+            controlled by unreachable objects """
+        
+        # We use the "Allow sendto" permission as by default it is
+        # controlled by the portal, which is unreachable via __parent__
+        # attributes on the comments.
+        permission = "Allow sendto"
+
+        # Unwrapped comments can't find the portal so just return manager
+        self.assertNotIn("Anonymous",
+             rolesForPermissionOn(permission,
+                                  self.unwrapped_archetypes_comment))
+        self.assertNotIn("Anonymous",
+             rolesForPermissionOn(permission,
+                                  self.unwrapped_dexterity_comment))
+        
+        # Wrapped objects can find the portal and correctly return the
+        # anonymous role.
+        self.assertIn("Anonymous",
+             rolesForPermissionOn(permission,
+                                  self.wrapped_archetypes_comment))
+        self.assertIn("Anonymous",
+             rolesForPermissionOn(permission,
+                                  self.wrapped_dexterity_comment))
+    
+    def test_acquiring_comment_permissions_via_user_nobody(self):
+        """ The current implementation uses user_nobody.has_permission to
+            check whether anonymous can view comments.  This confirms it also
+            works. """        
+        
+        # Again we want to use a permission that's not managed by any of our
+        # content objects so it must be acquired from the portal.
+        permission = "Allow sendto"
+    
+        self.assertFalse(
+             user_nobody.has_permission(permission,
+                                        self.unwrapped_archetypes_comment))
+        
+        self.assertFalse(
+             user_nobody.has_permission(permission,
+                                        self.unwrapped_dexterity_comment))
+
+        self.assertTrue(
+             user_nobody.has_permission(permission,
+                                        self.wrapped_archetypes_comment))
+
+        self.assertTrue(
+             user_nobody.has_permission(permission,
+                                        self.wrapped_dexterity_comment))
 
 
 def test_suite():


Repository: plone.app.discussion
Branch: refs/heads/master
Date: 2013-11-25T12:52:31Z
Author: Alan Hoey (evilbungle) <alan.hoey at gmail.com>
Commit: https://github.com/plone/plone.app.discussion/commit/e6ec6ebe164b68f465fe87ea3dda8e7136411053

Add tests to confirm that some methods of a conversation incorrectly use an unwrapped comment to determine whether anonymous users can view.

Files changed:
M plone/app/discussion/tests/test_acquisition.py

diff --git a/plone/app/discussion/tests/test_acquisition.py b/plone/app/discussion/tests/test_acquisition.py
index 72d68fe..c6a96db 100644
--- a/plone/app/discussion/tests/test_acquisition.py
+++ b/plone/app/discussion/tests/test_acquisition.py
@@ -2,7 +2,7 @@
 from AccessControl.User import User  # before SpecialUsers
 from AccessControl.SpecialUsers import nobody as user_nobody
 from AccessControl.PermissionRole import rolesForPermissionOn
-from Acquisition import aq_chain
+from Acquisition import aq_chain, aq_base
 from plone.app.discussion.testing import \
     PLONE_APP_DISCUSSION_INTEGRATION_TESTING
 from plone.app.discussion.interfaces import IConversation
@@ -173,6 +173,71 @@ def test_acquiring_comment_permissions_via_user_nobody(self):
              user_nobody.has_permission(permission,
                                         self.wrapped_dexterity_comment))
 
+class AcquiredPermissionTest(unittest.TestCase):
+    """ Test methods of a conversation which rely on acquired permissions """
+
+    layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
+
+    def setUp(self):
+        self.portal = self.layer['portal']
+        self.request = self.layer['request']
+        setRoles(self.portal, TEST_USER_ID, ['Manager'])
+        self.wftool = getToolByName(self.portal, 'portal_workflow')
+
+        # Disable workflow for comments and content.
+        self.wftool.setChainForPortalTypes(["Discussion Item"],[])
+        self.wftool.setChainForPortalTypes([dexterity_type_name],[])
+
+        # Create a dexterity item.
+        self.portal.invokeFactory(
+            id=dexterity_object_id,
+            title='Instance Of Dexterity Type',
+            type_name=dexterity_type_name,
+        )
+        
+        self.content = self.portal.get(dexterity_object_id)
+
+        # Absolutely make sure that we're replicating the case of an 
+        # incomplete chain correctly.
+        aq_base(self.content).__parent__ = None
+        
+        self.conversation = IConversation(self.content)
+        
+        # Add a comment
+        comment = createObject('plone.Comment')
+        self.conversation.addComment(comment)
+        self.comment = comment
+    
+    def test_view_permission_is_only_available_on_portal(self):
+        """ Check that the test setup is correct """
+        
+        content_roles = rolesForPermissionOn("View",aq_base(self.content))
+        self.assertNotIn("Anonymous",content_roles)
+        
+        comment_roles = rolesForPermissionOn("View",aq_base(self.comment))
+        self.assertNotIn("Anonymous",comment_roles)
+        
+        # This actually acquires view from the app root, but we don't really
+        # care, we just need to confirm that something above our content
+        # object will give us View.
+        portal_roles = rolesForPermissionOn("View",self.portal)
+        self.assertIn("Anonymous",portal_roles)
+
+    # The following tests fail when the conversation uses unwrapped comment
+    # objects to determine whether an anonymous user has the view permission.
+        
+    def test_total_comments(self):
+        self.assertEqual(self.conversation.total_comments,1)
+    
+    def test_last_comment_date(self):
+        self.assertEqual(self.conversation.last_comment_date,
+                         self.comment.creation_date)
+    
+    def test_public_commentators(self):
+        self.assertEqual(self.conversation.public_commentators,
+                         (self.comment.author_username,))
+
+
 
 def test_suite():
     return unittest.defaultTestLoader.loadTestsFromName(__name__)


Repository: plone.app.discussion
Branch: refs/heads/master
Date: 2013-11-25T12:54:49Z
Author: Alan Hoey (evilbungle) <alan.hoey at gmail.com>
Commit: https://github.com/plone/plone.app.discussion/commit/8161dc14dae8cd8b7c9d796066d302b8a4a153c9

Fixing test failures.

Files changed:
M plone/app/discussion/conversation.py

diff --git a/plone/app/discussion/conversation.py b/plone/app/discussion/conversation.py
index 16a3cec..ca22c05 100644
--- a/plone/app/discussion/conversation.py
+++ b/plone/app/discussion/conversation.py
@@ -101,13 +101,13 @@ def total_comments(self):
         ]
         return len(public_comments)
 
-    @property
+    @computed_attribute_decorator(level=1)
     def last_comment_date(self):
         # self._comments is an Instance of a btree. The keys
         # are always ordered
         comment_keys = self._comments.keys()
         for comment_key in reversed(comment_keys):
-            comment = self._comments[comment_key]
+            comment = self[comment_key]
             if user_nobody.has_permission('View', comment):
                 return comment.creation_date
         return None
@@ -116,10 +116,10 @@ def last_comment_date(self):
     def commentators(self):
         return self._commentators
 
-    @property
+    @computed_attribute_decorator(level=1)
     def public_commentators(self):
         retval = set()
-        for comment in self._comments.values():
+        for comment in self.values():
             if not user_nobody.has_permission('View', comment):
                 continue
             retval.add(comment.author_username)


Repository: plone.app.discussion
Branch: refs/heads/master
Date: 2013-11-25T13:04:24Z
Author: Alan Hoey (evilbungle) <alan.hoey at gmail.com>
Commit: https://github.com/plone/plone.app.discussion/commit/1bf4dacc1d116e8c33691d01dbc1083fd004514a

Back out internal version number

Files changed:
M setup.py

diff --git a/setup.py b/setup.py
index 7a91fc2..af74549 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
 from setuptools import setup, find_packages
 
-version = '2.3.0dev.delib-da.1'
+version = '2.3.0dev'
 
 install_requires = [
     'setuptools',


Repository: plone.app.discussion
Branch: refs/heads/master
Date: 2014-05-12T22:53:34+02:00
Author: Timo Stollenwerk (tisto) <tisto at plone.org>
Commit: https://github.com/plone/plone.app.discussion/commit/e18598e316e824795e752e7bb1c90ee37dc1cc6f

Merge pull request #38 from delib/evilbungle-comment-acquisition

Fixing incorrect behaviour with acquired permissions

Files changed:
A plone/app/discussion/tests/configure.zcml
A plone/app/discussion/tests/profile/types.xml
A plone/app/discussion/tests/profile/types/sample_content_type.xml
A plone/app/discussion/tests/profile/workflows.xml
A plone/app/discussion/tests/profile/workflows/comment_workflow_acquired_view/definition.xml
A plone/app/discussion/tests/test_acquisition.py
M plone/app/discussion/conversation.py
M plone/app/discussion/testing.py

diff --git a/plone/app/discussion/conversation.py b/plone/app/discussion/conversation.py
index ba27c0f..ca22c05 100644
--- a/plone/app/discussion/conversation.py
+++ b/plone/app/discussion/conversation.py
@@ -50,8 +50,14 @@
 
 from AccessControl.SpecialUsers import nobody as user_nobody
 
+from ComputedAttribute import ComputedAttribute
+
 ANNOTATION_KEY = 'plone.app.discussion:conversation'
 
+def computed_attribute_decorator(level=0):
+    def computed_attribute_wrapper(func):
+        return ComputedAttribute(func, level)
+    return computed_attribute_wrapper
 
 class Conversation(Traversable, Persistent, Explicit):
     """A conversation is a container for all comments on a content object.
@@ -87,21 +93,21 @@ def enabled(self):
         parent = aq_inner(self.__parent__)
         return parent.restrictedTraverse('@@conversation_view').enabled()
 
-    @property
+    @computed_attribute_decorator(level=1)
     def total_comments(self):
         public_comments = [
-            x for x in self._comments.values()
+            x for x in self.values()
             if user_nobody.has_permission('View', x)
         ]
         return len(public_comments)
 
-    @property
+    @computed_attribute_decorator(level=1)
     def last_comment_date(self):
         # self._comments is an Instance of a btree. The keys
         # are always ordered
         comment_keys = self._comments.keys()
         for comment_key in reversed(comment_keys):
-            comment = self._comments[comment_key]
+            comment = self[comment_key]
             if user_nobody.has_permission('View', comment):
                 return comment.creation_date
         return None
@@ -110,10 +116,10 @@ def last_comment_date(self):
     def commentators(self):
         return self._commentators
 
-    @property
+    @computed_attribute_decorator(level=1)
     def public_commentators(self):
         retval = set()
-        for comment in self._comments.values():
+        for comment in self.values():
             if not user_nobody.has_permission('View', comment):
                 continue
             retval.add(comment.author_username)
diff --git a/plone/app/discussion/testing.py b/plone/app/discussion/testing.py
index e7a397d..e47026f 100644
--- a/plone/app/discussion/testing.py
+++ b/plone/app/discussion/testing.py
@@ -41,10 +41,14 @@ def setUpZope(self, app, configurationContext):
         xmlconfig.file('configure.zcml',
                        plone.app.discussion,
                        context=configurationContext)
+        xmlconfig.file('configure.zcml',
+                       plone.app.discussion.tests,
+                       context=configurationContext)
 
     def setUpPloneSite(self, portal):
         # Install into Plone site using portal_setup
         applyProfile(portal, 'plone.app.discussion:default')
+        applyProfile(portal, 'plone.app.discussion.tests:testing')
 
         # Creates some users
         acl_users = getToolByName(portal, 'acl_users')
diff --git a/plone/app/discussion/tests/configure.zcml b/plone/app/discussion/tests/configure.zcml
new file mode 100644
index 0000000..ee79b95
--- /dev/null
+++ b/plone/app/discussion/tests/configure.zcml
@@ -0,0 +1,16 @@
+<configure
+  xmlns="http://namespaces.zope.org/zope"
+  xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
+  i18n_domain="freitag.discussion">
+
+  <include package="plone.app.dexterity" />
+
+  <genericsetup:registerProfile
+    name="testing"
+    title="plone.app.discussion.testing"
+    directory="profile"
+    description="Testing profile for plone.app.discussion"
+    provides="Products.GenericSetup.interfaces.EXTENSION"
+    />
+
+</configure>
diff --git a/plone/app/discussion/tests/profile/types.xml b/plone/app/discussion/tests/profile/types.xml
new file mode 100644
index 0000000..0aec569
--- /dev/null
+++ b/plone/app/discussion/tests/profile/types.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<object name="portal_types" meta_type="Plone Types Tool">
+  <object name="sample_content_type" meta_type="Dexterity FTI" />
+</object>
diff --git a/plone/app/discussion/tests/profile/types/sample_content_type.xml b/plone/app/discussion/tests/profile/types/sample_content_type.xml
new file mode 100644
index 0000000..7869638
--- /dev/null
+++ b/plone/app/discussion/tests/profile/types/sample_content_type.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<object name="sample_content_type"
+   meta_type="Dexterity FTI"
+   i18n:domain="freitag.discussion" xmlns:i18n="http://xml.zope.org/namespaces/i18n">
+
+  <!-- Basic metadata -->
+  <property name="title" i18n:translate="">sample_content_type</property>
+  <property name="description"
+    i18n:translate="">Sample Content</property>
+  <property name="content_icon">document_icon.png</property>
+  <property name="global_allow">True</property>
+  <property name="filter_content_types">True</property>
+  <property name="allowed_content_types">
+  </property>
+  <property name="allow_discussion">True</property>
+
+  <property name="klass">plone.dexterity.content.Item</property>
+
+  <property name="add_permission">cmf.AddPortalContent</property>
+  <property name="behaviors">
+    <element value="plone.app.content.interfaces.INameFromTitle" />
+  </property>
+
+  <!-- View information -->
+  <property name="default_view">view</property>
+  <property name="default_view_fallback">False</property>
+  <property name="view_methods">
+    <element value="view" />
+  </property>
+
+  <!-- Method aliases -->
+  <alias from="(Default)" to="(selected layout)" />
+  <alias from="edit" to="@@edit" />
+  <alias from="sharing" to="@@sharing" />
+  <alias from="view" to="@@view" />
+
+  <!-- Actions -->
+  <action title="View" action_id="view" category="object" condition_expr=""
+    url_expr="string:${object_url}/" visible="True">
+    <permission value="View" />
+  </action>
+
+  <action title="Edit" action_id="edit" category="object" condition_expr=""
+    url_expr="string:${object_url}/edit" visible="True">
+    <permission value="Modify portal content" />
+  </action>
+</object>
diff --git a/plone/app/discussion/tests/profile/workflows.xml b/plone/app/discussion/tests/profile/workflows.xml
new file mode 100644
index 0000000..500f444
--- /dev/null
+++ b/plone/app/discussion/tests/profile/workflows.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<object name="portal_workflow" meta_type="CMF Workflow Tool">
+ <object name="comment_workflow_acquired_view" meta_type="Workflow"/>
+</object>
diff --git a/plone/app/discussion/tests/profile/workflows/comment_workflow_acquired_view/definition.xml b/plone/app/discussion/tests/profile/workflows/comment_workflow_acquired_view/definition.xml
new file mode 100644
index 0000000..89a9fb2
--- /dev/null
+++ b/plone/app/discussion/tests/profile/workflows/comment_workflow_acquired_view/definition.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+<dc-workflow
+    workflow_id="comment_workflow_acquired_view"
+    title="Single State Workflow"
+    description="- Essentially a workflow with no transitions, but has a Published state, so portlets and applications that expect that state will continue to work."
+    state_variable="review_state"
+    initial_state="published"
+    manager_bypass="False">
+ <permission>Access contents information</permission>
+ <permission>Change portal events</permission>
+ <permission>Modify portal content</permission>
+ <permission>View</permission>
+ <state state_id="published" title="Published">
+  <description>Visible to everyone, editable by the owner.</description>
+  <permission-map name="Access contents information" acquired="False">
+   <permission-role>Anonymous</permission-role>
+  </permission-map>
+  <permission-map name="Change portal events" acquired="False">
+   <permission-role>Editor</permission-role>
+   <permission-role>Manager</permission-role>
+   <permission-role>Owner</permission-role>
+   <permission-role>Site Administrator</permission-role>
+  </permission-map>
+  <permission-map name="Modify portal content" acquired="False">
+   <permission-role>Editor</permission-role>
+   <permission-role>Manager</permission-role>
+   <permission-role>Owner</permission-role>
+   <permission-role>Site Administrator</permission-role>
+  </permission-map>
+  <permission-map name="View" acquired="True">
+  </permission-map>
+ </state>
+ <variable variable_id="action" for_catalog="False" for_status="True" update_always="True">
+  <description>Previous transition</description>
+  <default>
+   <expression>transition/getId|nothing</expression>
+  </default>
+  <guard>
+  </guard>
+ </variable>
+ <variable variable_id="actor" for_catalog="False" for_status="True" update_always="True">
+  <description>The ID of the user who performed the previous transition</description>
+  <default>
+   <expression>user/getId</expression>
+  </default>
+  <guard>
+  </guard>
+ </variable>
+ <variable variable_id="comments" for_catalog="False" for_status="True" update_always="True">
+  <description>Comment about the last transition</description>
+  <default>
+   <expression>python:state_change.kwargs.get('comment', '')</expression>
+  </default>
+  <guard>
+  </guard>
+ </variable>
+ <variable variable_id="review_history" for_catalog="False" for_status="False" update_always="False">
+  <description>Provides access to workflow history</description>
+  <default>
+   <expression>state_change/getHistory</expression>
+  </default>
+  <guard>
+   <guard-permission>Request review</guard-permission>
+   <guard-permission>Review portal content</guard-permission>
+  </guard>
+ </variable>
+ <variable variable_id="time" for_catalog="False" for_status="True" update_always="True">
+  <description>When the previous transition was performed</description>
+  <default>
+   <expression>state_change/getDateTime</expression>
+  </default>
+  <guard>
+  </guard>
+ </variable>
+</dc-workflow>
diff --git a/plone/app/discussion/tests/test_acquisition.py b/plone/app/discussion/tests/test_acquisition.py
new file mode 100644
index 0000000..c6a96db
--- /dev/null
+++ b/plone/app/discussion/tests/test_acquisition.py
@@ -0,0 +1,243 @@
+# -*- coding: utf-8 -*-
+from AccessControl.User import User  # before SpecialUsers
+from AccessControl.SpecialUsers import nobody as user_nobody
+from AccessControl.PermissionRole import rolesForPermissionOn
+from Acquisition import aq_chain, aq_base
+from plone.app.discussion.testing import \
+    PLONE_APP_DISCUSSION_INTEGRATION_TESTING
+from plone.app.discussion.interfaces import IConversation
+from plone.app.testing import TEST_USER_ID, setRoles
+from Products.CMFCore.utils import getToolByName
+from zope.component import createObject
+
+import unittest2 as unittest
+
+
+dexterity_type_name = 'sample_content_type'
+dexterity_object_id = 'instance-of-dexterity-type'
+archetypes_object_id = 'instance-of-archetypes-type'
+one_state_workflow = 'one_state_workflow'
+comment_workflow_acquired_view = 'comment_workflow_acquired_view'
+
+class AcquisitionTest(unittest.TestCase):
+    """ Define the expected behaviour of wrapped and unwrapped comments. """
+
+    layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
+
+    def setUp(self):
+        self.portal = self.layer['portal']
+        self.request = self.layer['request']
+        setRoles(self.portal, TEST_USER_ID, ['Manager'])
+        self.wftool = getToolByName(self.portal, 'portal_workflow')
+
+        # Use customized workflow for comments.
+        self.wftool.setChainForPortalTypes(
+            ['Discussion Item'],
+            (comment_workflow_acquired_view,),
+        )
+
+        # Use one_state_workflow for Document and sample_content_type,
+        # so they're always published.
+        self.wftool.setChainForPortalTypes(
+            ['Document', dexterity_type_name],
+            (one_state_workflow,),
+        )
+
+        # Create a dexterity item and add a comment.
+        self.portal.invokeFactory(
+            id=dexterity_object_id,
+            title='Instance Of Dexterity Type',
+            type_name=dexterity_type_name,
+        )
+        
+        self.dexterity_object = self.portal.get(dexterity_object_id)
+        dx_conversation = IConversation(self.dexterity_object)
+        self.dexterity_conversation = dx_conversation
+        dx_comment = createObject('plone.Comment')
+        dx_conversation.addComment(dx_comment)
+        self.unwrapped_dexterity_comment = dx_comment
+        self.wrapped_dexterity_comment = dx_conversation[dx_comment.id]
+
+        # Create an Archetypes item and add a comment.
+        self.portal.invokeFactory(
+            id=archetypes_object_id,
+            title='Instance Of Archetypes Type',
+            type_name='Document',
+        )
+        
+        self.archetypes_object = self.portal.get(archetypes_object_id)
+        at_conversation = IConversation(self.archetypes_object)
+        self.archetypes_conversation = at_conversation
+        at_comment = createObject('plone.Comment')
+        at_conversation.addComment(at_comment)
+        self.unwrapped_archetypes_comment = at_comment
+        self.wrapped_archetypes_comment = at_conversation[at_comment.id]
+        
+
+    def test_workflows_installed(self):
+        """Check that the new comment workflow has been installed properly.
+        (Just a test to check our test setup.)
+        """
+        workflows = self.wftool.objectIds()
+        self.assertTrue('comment_workflow_acquired_view' in workflows)
+
+    def test_workflows_applied(self):
+        """Check that all objects have the workflow that we expect.
+        (Just a test to check our test setup.)"""
+        self.assertEqual(
+            self.wftool.getChainFor(self.archetypes_object),
+            (one_state_workflow,)
+        )
+        self.assertEqual(
+            self.wftool.getChainFor(self.dexterity_object),
+            (one_state_workflow,)
+        )
+        self.assertEqual(
+            self.wftool.getChainFor(self.unwrapped_archetypes_comment),
+            (comment_workflow_acquired_view,)
+        )
+        self.assertEqual(
+            self.wftool.getChainFor(self.unwrapped_dexterity_comment),
+            (comment_workflow_acquired_view,)
+        )
+
+    def test_comment_acquisition_chain(self):
+        """ Test that the acquisition chains for wrapped and unwrapped
+            comments are as expected. """
+        
+        # Unwrapped comments rely on __parent__ attributes to determine 
+        # parentage.  Frustratingly there is no guarantee that __parent__
+        # is always set, so the computed acquisition chain may be short.
+        # In this case the unwrapped AT and DX objects stored as the 
+        # conversation parents don't have a __parent__, preventing the portal
+        # from being included in the chain.
+        self.assertNotIn(self.portal,
+                         aq_chain(self.unwrapped_archetypes_comment))
+        self.assertNotIn(self.portal,
+                         aq_chain(self.unwrapped_dexterity_comment))
+
+        # Wrapped comments however have a complete chain and thus can find the
+        # portal object reliably.
+        self.assertIn(self.portal,aq_chain(self.wrapped_archetypes_comment))
+        self.assertIn(self.portal,aq_chain(self.wrapped_dexterity_comment))
+
+    
+    def test_acquiring_comment_permissions(self):
+        """ Unwrapped comments should not be able to acquire permissions
+            controlled by unreachable objects """
+        
+        # We use the "Allow sendto" permission as by default it is
+        # controlled by the portal, which is unreachable via __parent__
+        # attributes on the comments.
+        permission = "Allow sendto"
+
+        # Unwrapped comments can't find the portal so just return manager
+        self.assertNotIn("Anonymous",
+             rolesForPermissionOn(permission,
+                                  self.unwrapped_archetypes_comment))
+        self.assertNotIn("Anonymous",
+             rolesForPermissionOn(permission,
+                                  self.unwrapped_dexterity_comment))
+        
+        # Wrapped objects can find the portal and correctly return the
+        # anonymous role.
+        self.assertIn("Anonymous",
+             rolesForPermissionOn(permission,
+                                  self.wrapped_archetypes_comment))
+        self.assertIn("Anonymous",
+             rolesForPermissionOn(permission,
+                                  self.wrapped_dexterity_comment))
+    
+    def test_acquiring_comment_permissions_via_user_nobody(self):
+        """ The current implementation uses user_nobody.has_permission to
+            check whether anonymous can view comments.  This confirms it also
+            works. """        
+        
+        # Again we want to use a permission that's not managed by any of our
+        # content objects so it must be acquired from the portal.
+        permission = "Allow sendto"
+    
+        self.assertFalse(
+             user_nobody.has_permission(permission,
+                                        self.unwrapped_archetypes_comment))
+        
+        self.assertFalse(
+             user_nobody.has_permission(permission,
+                                        self.unwrapped_dexterity_comment))
+
+        self.assertTrue(
+             user_nobody.has_permission(permission,
+                                        self.wrapped_archetypes_comment))
+
+        self.assertTrue(
+             user_nobody.has_permission(permission,
+                                        self.wrapped_dexterity_comment))
+
+class AcquiredPermissionTest(unittest.TestCase):
+    """ Test methods of a conversation which rely on acquired permissions """
+
+    layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
+
+    def setUp(self):
+        self.portal = self.layer['portal']
+        self.request = self.layer['request']
+        setRoles(self.portal, TEST_USER_ID, ['Manager'])
+        self.wftool = getToolByName(self.portal, 'portal_workflow')
+
+        # Disable workflow for comments and content.
+        self.wftool.setChainForPortalTypes(["Discussion Item"],[])
+        self.wftool.setChainForPortalTypes([dexterity_type_name],[])
+
+        # Create a dexterity item.
+        self.portal.invokeFactory(
+            id=dexterity_object_id,
+            title='Instance Of Dexterity Type',
+            type_name=dexterity_type_name,
+        )
+        
+        self.content = self.portal.get(dexterity_object_id)
+
+        # Absolutely make sure that we're replicating the case of an 
+        # incomplete chain correctly.
+        aq_base(self.content).__parent__ = None
+        
+        self.conversation = IConversation(self.content)
+        
+        # Add a comment
+        comment = createObject('plone.Comment')
+        self.conversation.addComment(comment)
+        self.comment = comment
+    
+    def test_view_permission_is_only_available_on_portal(self):
+        """ Check that the test setup is correct """
+        
+        content_roles = rolesForPermissionOn("View",aq_base(self.content))
+        self.assertNotIn("Anonymous",content_roles)
+        
+        comment_roles = rolesForPermissionOn("View",aq_base(self.comment))
+        self.assertNotIn("Anonymous",comment_roles)
+        
+        # This actually acquires view from the app root, but we don't really
+        # care, we just need to confirm that something above our content
+        # object will give us View.
+        portal_roles = rolesForPermissionOn("View",self.portal)
+        self.assertIn("Anonymous",portal_roles)
+
+    # The following tests fail when the conversation uses unwrapped comment
+    # objects to determine whether an anonymous user has the view permission.
+        
+    def test_total_comments(self):
+        self.assertEqual(self.conversation.total_comments,1)
+    
+    def test_last_comment_date(self):
+        self.assertEqual(self.conversation.last_comment_date,
+                         self.comment.creation_date)
+    
+    def test_public_commentators(self):
+        self.assertEqual(self.conversation.public_commentators,
+                         (self.comment.author_username,))
+
+
+
+def test_suite():
+    return unittest.defaultTestLoader.loadTestsFromName(__name__)




-------------------------------------------------------------------------------
-------------- next part --------------
A non-text attachment was scrubbed...
Name: CHANGES.log
Type: application/octet-stream
Size: 56055 bytes
Desc: not available
URL: <http://lists.plone.org/pipermail/plone-testbot/attachments/20140512/4f1715d7/attachment-0002.obj>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: build.log
Type: application/octet-stream
Size: 86229 bytes
Desc: not available
URL: <http://lists.plone.org/pipermail/plone-testbot/attachments/20140512/4f1715d7/attachment-0003.obj>


More information about the Testbot mailing list