802e3ec04c
This is part of PloneHotfix20161129. Updated metadata.xml version to 1000. This leaves more room for profile version increases in earlier releases. We apply the rolemap step again to avoid accidentally missing it.
518 lines
18 KiB
Python
518 lines
18 KiB
Python
# -*- coding: utf-8 -*-
|
|
from plone.app.discussion.browser.comment import View
|
|
from plone.app.discussion.interfaces import IComment
|
|
from plone.app.discussion.interfaces import IConversation
|
|
from plone.app.discussion.interfaces import IReplies
|
|
from plone.app.discussion.testing import PLONE_APP_DISCUSSION_INTEGRATION_TESTING # noqa
|
|
from plone.app.testing import setRoles
|
|
from plone.app.testing import TEST_USER_ID
|
|
from Products.CMFCore.utils import getToolByName
|
|
from zope.component import createObject
|
|
from zope.component import getMultiAdapter
|
|
|
|
import datetime
|
|
import logging
|
|
import unittest2 as unittest
|
|
|
|
|
|
logger = logging.getLogger('plone.app.discussion.tests')
|
|
logger.addHandler(logging.StreamHandler())
|
|
|
|
|
|
class CommentTest(unittest.TestCase):
|
|
|
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
|
|
|
def setUp(self):
|
|
self.portal = self.layer['portal']
|
|
self.request = self.layer['request']
|
|
|
|
workflow = self.portal.portal_workflow
|
|
workflow.doActionFor(self.portal.doc1, 'publish')
|
|
|
|
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
|
self.catalog = getToolByName(self.portal, 'portal_catalog')
|
|
self.document_brain = self.catalog.searchResults(
|
|
portal_type='Document')[0]
|
|
|
|
def test_factory(self):
|
|
comment1 = createObject('plone.Comment')
|
|
self.assertTrue(IComment.providedBy(comment1))
|
|
|
|
def test_UTCDates(self):
|
|
utc_to_local_diff = \
|
|
datetime.datetime.now() - datetime.datetime.utcnow()
|
|
utc_to_local_diff = abs(utc_to_local_diff.seconds)
|
|
if utc_to_local_diff < 60:
|
|
logger.warning('Your computer is living in a timezone where local '
|
|
'time equals utc time. Some potential errors can '
|
|
'get hidden by that')
|
|
comment1 = createObject('plone.Comment')
|
|
local_utc = datetime.datetime.utcnow()
|
|
for date in (comment1.creation_date, comment1.modification_date):
|
|
difference = abs(date - local_utc)
|
|
difference = difference.seconds
|
|
# We hope that between comment1 and local_utc happen less than
|
|
# 10 seconds
|
|
self.assertFalse(difference / 10)
|
|
|
|
def test_id(self):
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.comment_id = 123
|
|
self.assertEqual('123', comment1.id)
|
|
self.assertEqual('123', comment1.getId())
|
|
self.assertEqual(u'123', comment1.__name__)
|
|
|
|
def test_uid(self):
|
|
conversation = IConversation(self.portal.doc1)
|
|
comment1 = createObject('plone.Comment')
|
|
conversation.addComment(comment1)
|
|
comment_brain = self.catalog.searchResults(
|
|
portal_type='Discussion Item'
|
|
)[0]
|
|
self.assertTrue(comment_brain.UID)
|
|
|
|
def test_uid_is_unique(self):
|
|
conversation = IConversation(self.portal.doc1)
|
|
comment1 = createObject('plone.Comment')
|
|
conversation.addComment(comment1)
|
|
comment2 = createObject('plone.Comment')
|
|
conversation.addComment(comment2)
|
|
brains = self.catalog.searchResults(
|
|
portal_type='Discussion Item'
|
|
)
|
|
self.assertNotEqual(brains[0].UID, brains[1].UID)
|
|
|
|
def test_comment_uid_differs_from_content_uid(self):
|
|
conversation = IConversation(self.portal.doc1)
|
|
comment1 = createObject('plone.Comment')
|
|
conversation.addComment(comment1)
|
|
comment_brain = self.catalog.searchResults(
|
|
portal_type='Discussion Item'
|
|
)[0]
|
|
self.assertNotEqual(self.document_brain.UID, comment_brain.UID)
|
|
|
|
def test_title(self):
|
|
conversation = IConversation(self.portal.doc1)
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.author_name = 'Jim Fulton'
|
|
conversation.addComment(comment1)
|
|
self.assertEqual('Jim Fulton on Document 1', comment1.Title())
|
|
|
|
def test_no_name_title(self):
|
|
conversation = IConversation(self.portal.doc1)
|
|
comment1 = createObject('plone.Comment')
|
|
conversation.addComment(comment1)
|
|
self.assertEqual('Anonymous on Document 1', comment1.Title())
|
|
|
|
def test_title_special_characters(self):
|
|
self.portal.invokeFactory(
|
|
id='doc_sp_chars',
|
|
title=u'Document äüö',
|
|
type_name='Document'
|
|
)
|
|
conversation = IConversation(self.portal.doc_sp_chars)
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.author_name = u'Tarek Ziadé'
|
|
conversation.addComment(comment1)
|
|
self.assertEqual(u'Tarek Ziadé on Document äüö', comment1.Title())
|
|
|
|
def test_title_special_characters_utf8(self):
|
|
self.portal.invokeFactory(
|
|
id='doc_sp_chars_utf8',
|
|
title='Document ëïû',
|
|
type_name='Document'
|
|
)
|
|
conversation = IConversation(self.portal.doc_sp_chars_utf8)
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.author_name = 'Hüüb Bôûmä'
|
|
conversation.addComment(comment1)
|
|
self.assertEqual(u'Hüüb Bôûmä on Document ëïû', comment1.Title())
|
|
|
|
def test_creator(self):
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.creator = 'jim'
|
|
self.assertEqual('jim', comment1.Creator())
|
|
|
|
def test_creator_author_name(self):
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.author_name = 'joey'
|
|
self.assertEqual('joey', comment1.Creator())
|
|
|
|
def test_owner(self):
|
|
comment1 = createObject('plone.Comment')
|
|
self.assertEqual((['plone', 'acl_users'], TEST_USER_ID),
|
|
comment1.getOwnerTuple())
|
|
|
|
def test_type(self):
|
|
comment1 = createObject('plone.Comment')
|
|
self.assertEqual(comment1.Type(), 'Comment')
|
|
|
|
def test_mime_type(self):
|
|
comment1 = createObject('plone.Comment')
|
|
self.assertEqual(comment1.mime_type, 'text/plain')
|
|
|
|
def test_getText(self):
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.text = 'First paragraph\n\nSecond_paragraph'
|
|
self.assertEqual(
|
|
''.join(comment1.getText().split()),
|
|
'<p>Firstparagraph<br/><br/>Second_paragraph</p>'
|
|
)
|
|
|
|
def test_getText_escapes_HTML(self):
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.text = '<b>Got HTML?</b>'
|
|
self.assertEqual(
|
|
comment1.getText(),
|
|
'<p><b>Got HTML?</b></p>'
|
|
)
|
|
|
|
def test_getText_with_non_ascii_characters(self):
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.text = u'Umlaute sind ä, ö und ü.'
|
|
self.assertEqual(
|
|
comment1.getText(),
|
|
'<p>Umlaute sind \xc3\xa4, \xc3\xb6 und \xc3\xbc.</p>'
|
|
)
|
|
|
|
def test_getText_doesnt_link(self):
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.text = 'Go to http://www.plone.org'
|
|
self.assertEqual(
|
|
comment1.getText(),
|
|
'<p>Go to http://www.plone.org</p>'
|
|
)
|
|
|
|
def test_getText_uses_comment_mime_type(self):
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.text = 'Go to http://www.plone.org'
|
|
comment1.mime_type = 'text/x-web-intelligent'
|
|
self.assertEqual(
|
|
comment1.getText(),
|
|
'Go to <a href="http://www.plone.org" ' +
|
|
'rel="nofollow">http://www.plone.org</a>'
|
|
)
|
|
|
|
def test_getText_uses_comment_mime_type_html(self):
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.text = 'Go to <a href="http://www.plone.org">plone.org</a>'
|
|
comment1.mime_type = 'text/html'
|
|
self.assertEqual(
|
|
comment1.getText(),
|
|
'Go to <a href="http://www.plone.org">plone.org</a>'
|
|
)
|
|
|
|
def test_getText_w_custom_targetMimetype(self):
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.text = 'para'
|
|
self.assertEqual(comment1.getText(targetMimetype='text/plain'), 'para')
|
|
|
|
def test_getText_invalid_transformation_raises_error(self):
|
|
conversation = IConversation(self.portal.doc1)
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.mime_type = 'text/x-html-safe'
|
|
comment1.text = 'para'
|
|
conversation.addComment(comment1)
|
|
self.assertEqual(
|
|
comment1.getText(targetMimetype='text/html'),
|
|
'para')
|
|
|
|
def test_traversal(self):
|
|
# make sure comments are traversable, have an id, absolute_url and
|
|
# physical path
|
|
|
|
conversation = IConversation(self.portal.doc1)
|
|
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.text = 'Comment text'
|
|
|
|
new_comment1_id = conversation.addComment(comment1)
|
|
|
|
comment = self.portal.doc1.restrictedTraverse(
|
|
'++conversation++default/{0}'.format(new_comment1_id)
|
|
)
|
|
self.assertTrue(IComment.providedBy(comment))
|
|
|
|
self.assertEqual(
|
|
(
|
|
'', 'plone', 'doc1', '++conversation++default',
|
|
str(new_comment1_id)
|
|
),
|
|
comment.getPhysicalPath()
|
|
)
|
|
self.assertEqual(
|
|
'http://nohost/plone/doc1/++conversation++default/' +
|
|
str(new_comment1_id), comment.absolute_url()
|
|
)
|
|
|
|
def test_view_blob_types(self):
|
|
"""
|
|
Make sure that traversal to images/files redirects to the
|
|
version of the url with a /view in it.
|
|
"""
|
|
self.portal.invokeFactory(
|
|
id='image1',
|
|
title='Image',
|
|
type_name='Image'
|
|
)
|
|
conversation = IConversation(self.portal.image1)
|
|
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.text = 'Comment text'
|
|
new_comment1_id = conversation.addComment(comment1)
|
|
comment = self.portal.image1.restrictedTraverse(
|
|
'++conversation++default/{0}'.format(new_comment1_id)
|
|
)
|
|
|
|
view = View(comment, self.request)
|
|
View.__call__(view)
|
|
response = self.request.response
|
|
self.assertIn('/view', response.headers['location'])
|
|
|
|
def test_workflow(self):
|
|
"""Basic test for the 'comment_review_workflow'
|
|
"""
|
|
self.portal.portal_workflow.setChainForPortalTypes(
|
|
('Discussion Item',),
|
|
('comment_review_workflow,'))
|
|
|
|
conversation = IConversation(self.portal.doc1)
|
|
comment1 = createObject('plone.Comment')
|
|
new_comment1_id = conversation.addComment(comment1)
|
|
|
|
comment = conversation[new_comment1_id]
|
|
|
|
# Make sure comments use the 'comment_review_workflow'
|
|
chain = self.portal.portal_workflow.getChainFor(comment)
|
|
self.assertEqual(('comment_review_workflow',), chain)
|
|
|
|
# Ensure the initial state was entered and recorded
|
|
self.assertEqual(
|
|
1,
|
|
len(comment.workflow_history['comment_review_workflow'])
|
|
)
|
|
self.assertEqual(
|
|
None,
|
|
comment.workflow_history['comment_review_workflow'][0]['action']
|
|
)
|
|
self.assertEqual(
|
|
'pending',
|
|
self.portal.portal_workflow.getInfoFor(comment, 'review_state')
|
|
)
|
|
|
|
def test_fti(self):
|
|
# test that we can look up an FTI for Discussion Item
|
|
|
|
self.assertIn(
|
|
'Discussion Item',
|
|
self.portal.portal_types.objectIds()
|
|
)
|
|
|
|
comment1 = createObject('plone.Comment')
|
|
|
|
fti = self.portal.portal_types.getTypeInfo(comment1)
|
|
self.assertEqual('Discussion Item', fti.getTypeInfo(comment1).getId())
|
|
|
|
def test_view(self):
|
|
# make sure that the comment view is there and redirects to the right
|
|
# URL
|
|
|
|
# Create a conversation. In this case we doesn't assign it to an
|
|
# object, as we just want to check the Conversation object API.
|
|
conversation = IConversation(self.portal.doc1)
|
|
|
|
# Create a comment
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.text = 'Comment text'
|
|
|
|
# Add comment to the conversation
|
|
new_comment1_id = conversation.addComment(comment1)
|
|
|
|
comment = self.portal.doc1.restrictedTraverse(
|
|
'++conversation++default/{0}'.format(new_comment1_id)
|
|
)
|
|
|
|
# make sure the view is there
|
|
self.assertTrue(getMultiAdapter((comment, self.request),
|
|
name='view'))
|
|
|
|
# make sure the HTTP redirect (status code 302) works when a comment
|
|
# is called directly
|
|
view = View(comment, self.request)
|
|
View.__call__(view)
|
|
self.assertEqual(self.request.response.status, 302)
|
|
|
|
|
|
class RepliesTest(unittest.TestCase):
|
|
|
|
# test the IReplies adapter on a comment
|
|
|
|
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
|
|
|
|
def setUp(self):
|
|
self.portal = self.layer['portal']
|
|
setRoles(self.portal, TEST_USER_ID, ['Manager'])
|
|
|
|
workflow = self.portal.portal_workflow
|
|
workflow.doActionFor(self.portal.doc1, 'publish')
|
|
|
|
def test_add_comment(self):
|
|
# Add comments to a CommentReplies adapter
|
|
|
|
# Create a conversation. In this case we doesn't assign it to an
|
|
# object, as we just want to check the Conversation object API.
|
|
conversation = IConversation(self.portal.doc1)
|
|
|
|
# Add a comment to the conversation
|
|
replies = IReplies(conversation)
|
|
|
|
comment = createObject('plone.Comment')
|
|
comment.text = 'Comment text'
|
|
new_id = replies.addComment(comment)
|
|
comment = self.portal.doc1.restrictedTraverse(
|
|
'++conversation++default/{0}'.format(new_id)
|
|
)
|
|
|
|
# Add a reply to the CommentReplies adapter of the first comment
|
|
re_comment = createObject('plone.Comment')
|
|
re_comment.text = 'Comment text'
|
|
|
|
replies = IReplies(comment)
|
|
|
|
new_re_id = replies.addComment(re_comment)
|
|
|
|
# check that replies provides the IReplies interface
|
|
self.assertTrue(IReplies.providedBy(replies))
|
|
|
|
# Make sure our comment was added
|
|
self.assertTrue(new_re_id in replies)
|
|
|
|
# Make sure it is also reflected in the conversation
|
|
self.assertTrue(new_re_id in conversation)
|
|
|
|
# Make sure the conversation has the correct comment id
|
|
self.assertEqual(conversation[new_re_id].comment_id, new_re_id)
|
|
|
|
def test_delete_comment(self):
|
|
# Add and remove a comment to a CommentReplies adapter
|
|
|
|
# Create a conversation. In this case we doesn't assign it to an
|
|
# object, as we just want to check the Conversation object API.
|
|
conversation = IConversation(self.portal.doc1)
|
|
|
|
# Add a comment to the conversation
|
|
replies = IReplies(conversation)
|
|
|
|
comment = createObject('plone.Comment')
|
|
comment.text = 'Comment text'
|
|
new_id = replies.addComment(comment)
|
|
comment = self.portal.doc1.restrictedTraverse(
|
|
'++conversation++default/{0}'.format(new_id)
|
|
)
|
|
|
|
# Add a reply to the CommentReplies adapter of the first comment
|
|
re_comment = createObject('plone.Comment')
|
|
re_comment.text = 'Comment text'
|
|
|
|
replies = IReplies(comment)
|
|
|
|
new_re_id = replies.addComment(re_comment)
|
|
|
|
# Remove the reply to the CommentReplies adapter
|
|
del replies[new_re_id]
|
|
|
|
# Make sure there is no comment left in CommentReplies
|
|
self.assertEqual(len(replies), 0)
|
|
|
|
# Make sure the first comment is still in the conversation
|
|
self.assertEqual(conversation.total_comments(), 1)
|
|
|
|
def test_traversal(self):
|
|
# Create a nested structure of comment replies and check the traversal
|
|
|
|
# make sure comments are traversable, have an id, absolute_url and
|
|
# physical path
|
|
conversation = IConversation(self.portal.doc1)
|
|
|
|
comment1 = createObject('plone.Comment')
|
|
comment1.text = 'Comment text'
|
|
|
|
conversation.addComment(comment1)
|
|
|
|
comment = createObject('plone.Comment')
|
|
comment.text = 'Comment text'
|
|
new_id = conversation.addComment(comment)
|
|
comment = self.portal.doc1.restrictedTraverse(
|
|
'++conversation++default/{0}'.format(new_id)
|
|
)
|
|
|
|
# Add a reply to the CommentReplies adapter of the first comment
|
|
re_comment = createObject('plone.Comment')
|
|
re_comment.text = 'Comment text'
|
|
replies = IReplies(comment)
|
|
new_re_id = replies.addComment(re_comment)
|
|
re_comment = self.portal.doc1.restrictedTraverse(
|
|
'++conversation++default/{0}'.format(new_re_id)
|
|
)
|
|
|
|
# Add a reply to the reply
|
|
re_re_comment = createObject('plone.Comment')
|
|
re_re_comment.text = 'Comment text'
|
|
replies = IReplies(re_comment)
|
|
new_re_re_id = replies.addComment(re_re_comment)
|
|
re_re_comment = self.portal.doc1.restrictedTraverse(
|
|
'++conversation++default/{0}'.format(new_re_re_id)
|
|
)
|
|
|
|
# Add a reply to the replies reply
|
|
re_re_re_comment = createObject('plone.Comment')
|
|
re_re_re_comment.text = 'Comment text'
|
|
replies = IReplies(re_re_comment)
|
|
new_re_re_re_id = replies.addComment(re_re_re_comment)
|
|
re_re_re_comment = self.portal.doc1.restrictedTraverse(
|
|
'++conversation++default/{0}'.format(new_re_re_re_id)
|
|
)
|
|
|
|
self.assertEqual(
|
|
('', 'plone', 'doc1', '++conversation++default', str(new_id)),
|
|
comment.getPhysicalPath()
|
|
)
|
|
self.assertEqual(
|
|
'http://nohost/plone/doc1/++conversation++default/' +
|
|
str(new_id), comment.absolute_url()
|
|
)
|
|
self.assertEqual(
|
|
('', 'plone', 'doc1', '++conversation++default', str(new_re_id)),
|
|
re_comment.getPhysicalPath()
|
|
)
|
|
self.assertEqual(
|
|
'http://nohost/plone/doc1/++conversation++default/' +
|
|
str(new_re_id),
|
|
re_comment.absolute_url()
|
|
)
|
|
self.assertEqual(
|
|
(
|
|
'', 'plone', 'doc1', '++conversation++default',
|
|
str(new_re_re_id)
|
|
),
|
|
re_re_comment.getPhysicalPath()
|
|
)
|
|
self.assertEqual(
|
|
'http://nohost/plone/doc1/++conversation++default/' +
|
|
str(new_re_re_id),
|
|
re_re_comment.absolute_url()
|
|
)
|
|
self.assertEqual(
|
|
(
|
|
'', 'plone', 'doc1', '++conversation++default',
|
|
str(new_re_re_re_id)
|
|
),
|
|
re_re_re_comment.getPhysicalPath()
|
|
)
|
|
self.assertEqual(
|
|
'http://nohost/plone/doc1/++conversation++default/' +
|
|
str(new_re_re_re_id),
|
|
re_re_re_comment.absolute_url()
|
|
)
|