diff --git a/CHANGES.rst b/CHANGES.rst index 4c5201b..c870af4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,9 @@ New: Fixes: +- Refactor doctest to an integration test and skip it for Zope 4 due to isolation problems. + [pbauer] + - Changed i18n_domain to "plone" [claytonc] diff --git a/plone/app/lockingbehavior/testing.py b/plone/app/lockingbehavior/testing.py index a41c4b7..c486860 100644 --- a/plone/app/lockingbehavior/testing.py +++ b/plone/app/lockingbehavior/testing.py @@ -2,7 +2,6 @@ from plone.app.testing import PLONE_FIXTURE from plone.app.testing import IntegrationTesting from plone.app.testing import FunctionalTesting -from zope.configuration import xmlconfig class LockingLayer(PloneSandboxLayer): @@ -12,11 +11,7 @@ class LockingLayer(PloneSandboxLayer): def setUpZope(self, app, configurationContext): # Load ZCML import plone.app.lockingbehavior - xmlconfig.file('configure.zcml', plone.app.lockingbehavior, - context=configurationContext) - - def setUpPloneSite(self, portal): - pass + self.loadZCML(package=plone.app.lockingbehavior) LOCKING_FIXTURE = LockingLayer() diff --git a/plone/app/lockingbehavior/tests/locking.txt b/plone/app/lockingbehavior/tests/locking.txt deleted file mode 100644 index 1351439..0000000 --- a/plone/app/lockingbehavior/tests/locking.txt +++ /dev/null @@ -1,152 +0,0 @@ -Integration test for the lockingbehavior -======================================== - -Imports: - - >>> from plone.testing.z2 import Browser - >>> from plone.app.testing import setRoles - >>> from plone.dexterity.fti import DexterityFTI - >>> from plone.dexterity.utils import createContentInContainer - >>> from plone.protect.authenticator import _getKeyring - >>> import hmac - >>> from hashlib import sha1 as sha - >>> ring = _getKeyring('foo') - >>> secret = ring.random() - >>> foo_token = hmac.new(secret, 'foo', sha).hexdigest() - >>> bar_token = hmac.new(secret, 'bar', sha).hexdigest() - >>> import transaction - -Globals: - - >>> app = layer['app'] - >>> portal = layer['portal'] - - -For testing the locking, we need two users. Let's create "foo" and -"bar": - - >>> acl_users = portal.acl_users - >>> acl_users.userFolderAddUser('foo', 'foo', ['Member'], []) - >>> setRoles(portal, 'foo', ['Member', 'Manager', 'Contributor', 'Editor', 'Reader']) - >>> acl_users.userFolderAddUser('bar', 'bar', ['Member'], []) - >>> setRoles(portal, 'bar', ['Member', 'Manager', 'Contributor', 'Editor', 'Reader']) - >>> transaction.commit() - - -Log in both users with an own browser each: - - >>> foo_browser = Browser(app) - >>> foo_browser.handleErrors = False - >>> foo_browser.open(portal.absolute_url() + '/login_form') - >>> foo_browser.getControl(name='__ac_name').value = 'foo' - >>> foo_browser.getControl(name='__ac_password').value = 'foo' - >>> foo_browser.getControl(name='submit').click() - >>> 'You are now logged in' in foo_browser.contents - True - - - >>> bar_browser = Browser(app) - >>> bar_browser.handleErrors = False - >>> bar_browser.open(portal.absolute_url() + '/login_form') - >>> bar_browser.getControl(name='__ac_name').value = 'bar' - >>> bar_browser.getControl(name='__ac_password').value = 'bar' - >>> bar_browser.getControl(name='submit').click() - >>> 'You are now logged in' in bar_browser.contents - True - - -Let's create a FTI for testing with: - - >>> fti = DexterityFTI('LockableType', - ... factory='LockableType') - >>> fti.behaviors = ( - ... 'plone.app.lockingbehavior.behaviors.ILocking', - ... ) - >>> fti.global_allow = True - >>> portal.portal_types._setObject('LockableType', fti) - 'LockableType' - >>> schema = fti.lookupSchema() - -Create an object: - - >>> obj = createContentInContainer(portal, 'LockableType', - ... title='lockable object', - ... checkConstraints=False) - >>> obj - - >>> transaction.commit() - - -Now let foo edit the object: - - >>> foo_browser.open(obj.absolute_url()) - >>> foo_browser.url - 'http://nohost/plone/lockabletype' - >>> foo_browser.open('http://nohost/plone/lockabletype/edit?_authenticator=' + foo_token) - >>> foo_browser.getControl('Save') - - - -It should be locked for bar at the moment: - - >>> bar_browser.open('http://nohost/plone/lockabletype') - >>> 'This item was locked' in bar_browser.contents - True - -When bar tries to access /edit directly, he will be redirected back: - - >>> bar_browser.open('http://nohost/plone/lockabletype/edit?_authenticator=' + bar_token) - Traceback (most recent call last): - ... - Redirect: http://nohost/plone/lockabletype/@@view - -New let foo save the object: - - >>> foo_browser.getControl('Save').click() - >>> foo_browser.url - 'http://nohost/plone/lockabletype' - - -It should not be locked any more for bar: - - >>> bar_browser.open('http://nohost/plone/lockabletype') - >>> 'This item was locked' in bar_browser.contents - False - -Now let bar edit the object: - - >>> bar_browser.open('http://nohost/plone/lockabletype/edit?_authenticator=' + bar_token) - >>> bar_browser.getControl('Save') - - -Now its locked for foo: - - >>> foo_browser.open('http://nohost/plone/lockabletype') - >>> 'This item was locked' in foo_browser.contents - True - -Let bar cancel the edit: - - >>> bar_browser.getControl('Cancel').click() - >>> bar_browser.url - 'http://nohost/plone/lockabletype' - >>> 'This item was locked' in bar_browser.contents - False - -It should not be locked for foo now: - - >>> foo_browser.open('http://nohost/plone/lockabletype') - >>> 'This item was locked' in foo_browser.contents - False - -If we edit a content without chanig anything, the lock should also be -released: - - >>> bar_browser.open('http://nohost/plone/lockabletype/edit?_authenticator=' + bar_token) - >>> foo_browser.open('http://nohost/plone/lockabletype') - >>> 'This item was locked' in foo_browser.contents - True - >>> bar_browser.getControl('Save').click() - >>> foo_browser.open('http://nohost/plone/lockabletype') - >>> 'This item was locked' in foo_browser.contents - False diff --git a/plone/app/lockingbehavior/tests/test_doctests.py b/plone/app/lockingbehavior/tests/test_doctests.py deleted file mode 100644 index 982b978..0000000 --- a/plone/app/lockingbehavior/tests/test_doctests.py +++ /dev/null @@ -1,14 +0,0 @@ -import unittest2 as unittest -import doctest -from plone.testing import layered -from plone.app.lockingbehavior.testing import LOCKING_INTEGRATION_TESTING - - -def test_suite(): - suite = unittest.TestSuite() - suite.addTests([ - layered(doctest.DocFileSuite('locking.txt'), - layer=LOCKING_INTEGRATION_TESTING), - ]) - return suite - diff --git a/plone/app/lockingbehavior/tests/test_locking.py b/plone/app/lockingbehavior/tests/test_locking.py new file mode 100644 index 0000000..1a6cef8 --- /dev/null +++ b/plone/app/lockingbehavior/tests/test_locking.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +import unittest +from pkg_resources import get_distribution +from plone.app.lockingbehavior.testing import LOCKING_INTEGRATION_TESTING +from plone.app.testing import SITE_OWNER_NAME +from plone.app.testing import SITE_OWNER_PASSWORD +from plone.app.testing import TEST_USER_NAME +from plone.app.testing import TEST_USER_ID +from plone.app.testing import TEST_USER_PASSWORD +from plone.app.testing import setRoles +from plone.dexterity.fti import DexterityFTI +from plone.testing import z2 +import transaction + +has_zope4 = get_distribution('Zope2').version.startswith('4') + + +class TestLockingBehavior(unittest.TestCase): + + layer = LOCKING_INTEGRATION_TESTING + + def setUp(self): + # add IShortName behavior to Page + self.portal = self.layer['portal'] + self.request = self.layer['request'] + setRoles(self.portal, TEST_USER_ID, ['Manager']) + + fti = DexterityFTI('LockableType', + factory='LockableType') + fti.behaviors = ('plone.app.lockingbehavior.behaviors.ILocking', ) + fti.global_allow = True + self.portal.portal_types._setObject('LockableType', fti) + transaction.commit() + + # prepare two browsers + self.foo_browser = z2.Browser(self.layer['app']) + self.foo_browser.addHeader( + 'Authorization', 'Basic %s:%s' + % (SITE_OWNER_NAME, SITE_OWNER_PASSWORD,) + ) + self.foo_browser.open('http://nohost/plone') + + self.bar_browser = z2.Browser(self.layer['app']) + self.bar_browser.addHeader( + 'Authorization', 'Basic %s:%s' + % (TEST_USER_NAME, TEST_USER_PASSWORD,) + ) + self.bar_browser.open('http://nohost/plone') + + @unittest.skipIf(has_zope4, 'Test-isolation issues with Zope4') + def test_lockablebehavior(self): + # Add a lockable item + self.portal.invokeFactory( + 'LockableType', id='lockabletype', title='Lockable Type') + obj = self.portal['lockabletype'] + transaction.commit() + + # Edit it with User1 + self.foo_browser.open(obj.absolute_url()) + self.assertEqual( + self.foo_browser.url, 'http://nohost/plone/lockabletype') + self.foo_browser.getLink('Edit').click() + + # Is locked for User2 + self.bar_browser.open(obj.absolute_url()) + self.assertIn('This item was locked', self.bar_browser.contents) + + # Clicking Edit will redirect to view for User2 + self.bar_browser.getLink('Edit').click() + self.assertEqual( + self.bar_browser.url, 'http://nohost/plone/lockabletype/@@view') + self.assertIn('This item was locked', self.bar_browser.contents) + + # Unlock it + self.foo_browser.getControl('Save').click() + + # Is now unlocked for User2 + self.bar_browser.open(obj.absolute_url()) + self.assertNotIn('This item was locked', self.bar_browser.contents) + + # Edit it with User2 + self.bar_browser.getLink('Edit').click() + self.assertIn( + 'http://nohost/plone/lockabletype/edit', self.bar_browser.url) + self.assertNotIn('This item was locked', self.bar_browser.contents) + + # Is locked for User1 + self.foo_browser.open(obj.absolute_url()) + self.assertIn('This item was locked', self.foo_browser.contents) + + # Releasing the lock by closing the window by User1 does not work + self.bar_browser.open(obj.absolute_url()) + + # The obj is still locked + self.foo_browser.open(obj.absolute_url()) + self.assertIn('This item was locked', self.foo_browser.contents) + + # Instead you need to release it by saving + self.bar_browser.getLink('Edit').click() + self.bar_browser.getControl('Save').click() + + # Now it is unlocked + self.foo_browser.open(obj.absolute_url()) + self.assertNotIn('This item was locked', self.foo_browser.contents)