import unittest
 
from zope.component import provideHandler
import zope.component.testing
 
from AccessControl.PermissionRole import rolesForPermissionOn
 
from Products.CMFCore.testing import TraversingEventZCMLLayer
from Products.CMFCore.tests.base.dummy import DummyContent
from Products.CMFCore.tests.base.dummy import DummySite
from Products.CMFCore.tests.base.dummy import DummyTool
from Products.CMFCore.WorkflowTool import WorkflowTool
 
from Products.DCWorkflow.DCWorkflow import DCWorkflowDefinition
 
from collective.subtractiveworkflow.workflow import SubtractiveWorkflowDefinition
from collective.subtractiveworkflow import react
 
 
class SubtractiveWorkflowStandaloneTests(unittest.TestCase):
 
    layer = TraversingEventZCMLLayer
 
    def setUp(self):
        self.site = DummySite('site')
        self.site._setObject('portal_types', DummyTool())
        self.site._setObject('portal_workflow', WorkflowTool())
        self._constructDummyWorkflow()
 
        provideHandler(react.object_transitioned)
 
    def tearDown(self):
        zope.component.testing.tearDown()
 
    def _constructDummyWorkflow(self):
 
        wftool = self.site.portal_workflow
        wftool._setObject('wf', SubtractiveWorkflowDefinition('wf'))
        wftool.setDefaultChain('wf')
        wf = wftool.wf
 
        wf.permissions = ('View',)
 
        wf.states.addState('nonconfidential')
        sdef = wf.states['nonconfidential']
        sdef.setProperties(transitions=('make_confidential',))
        sdef.permission_roles = {'View': (),}
 
        wf.states.addState('confidential')
        sdef = wf.states['confidential']
        sdef.setProperties(transitions=('make_non_confidential',))
        sdef.permission_roles = {'View': ('Anonymous', 'Authenticated',),}
 
        wf.states.setInitialState('nonconfidential')
 
        wf.transitions.addTransition('make_confidential')
        tdef = wf.transitions['make_confidential']
        tdef.setProperties(title='', new_state_id='confidential', actbox_name='')
 
        wf.transitions.addTransition('make_non_confidential')
        tdef = wf.transitions['make_non_confidential']
        tdef.setProperties(title='', new_state_id='nonconfidential', actbox_name='')
 
        wf.variables.addVariable('comments')
        vdef = wf.variables['comments']
        vdef.setProperties(description='',
                 default_expr="python:state_change.kwargs.get('comment', '')",
                 for_status=1, update_always=1)
 
    def _getDummyWorkflow(self):
        wftool = self.site.portal_workflow
        return wftool.wf
 
    def test_doActionFor_mixture(self):
 
        wftool = self.site.portal_workflow
        wf = self._getDummyWorkflow()
 
        # This time, disallow Authenticated and Manager
        wf.states['confidential'].permission_roles = {'View': ('Authenticated', 'Manager'),}
 
        dummy = DummyContent() 
 
        # Now, if the item is normally granted to these roles
        dummy.manage_permission('View', ['Authenticated', 'Manager', 'Owner',], acquire=1)
 
        dummy = self.site._setObject('dummy', dummy)
 
        # These are the roles we know about
        self.assertEqual(['Anonymous', 'Authenticated', 'Manager', 'Owner'], sorted(dummy.validRoles()))
 
        self.assertEqual(wf._getStatusOf(dummy),
                         {'state': 'nonconfidential', 'comments': ''})
 
        # Then in the non-confidential state (no permissions ticked) we still have that role
        self.assertEquals(['Authenticated', 'Manager', 'Owner'], sorted(rolesForPermissionOn('View', dummy)))
 
        wf.doActionFor(dummy, 'make_confidential', comment='foo' )
        self.assertEqual(wf._getStatusOf(dummy),
                         {'state': 'confidential', 'comments': 'foo'})
 
        # But after moving to confidential, which disallows Anonymous and Authenticated,
        # we are left with Owner and Manager
        self.assertEquals(['Owner'], sorted(rolesForPermissionOn('View', dummy)))
 
    def test_doActionFor_anonymous(self):
 
        wftool = self.site.portal_workflow
        wf = self._getDummyWorkflow()
 
        dummy = DummyContent()
 
        # Now, if the item is normally granted to anonymous...
        dummy.manage_permission('View', ['Anonymous'], acquire=1)
 
        dummy = self.site._setObject('dummy', dummy)
 
        # These are the roles we know about
        self.assertEquals(['Anonymous', 'Authenticated', 'Manager', 'Owner'], sorted(dummy.validRoles()))
 
        self.assertEqual( wf._getStatusOf(dummy),
                          {'state': 'nonconfidential', 'comments': ''} )
 
        # Then in the non-confidential state (no permissions ticked) we still have that role
        self.assertEquals(['Anonymous'], sorted(rolesForPermissionOn('View', dummy)))
 
        wf.doActionFor(dummy, 'make_confidential', comment='foo' )
        self.assertEqual( wf._getStatusOf(dummy),
                          {'state': 'confidential', 'comments': 'foo'} )
 
        # But after moving to confidential, which disallows Anonymous and Authenticated,
        # we are left with Owner and Manager
        self.assertEquals(['Manager', 'Owner'], sorted(rolesForPermissionOn('View', dummy)))
 
 
class SubtractiveWorkflowStandaloneTestsConfidentialByDefault(unittest.TestCase):
 
    layer = TraversingEventZCMLLayer
 
    def setUp(self):
        self.site = DummySite('site')
        self.site._setObject('portal_types', DummyTool())
        self.site._setObject('portal_workflow', WorkflowTool())
        self._constructDummyWorkflow()
 
        provideHandler(react.object_transitioned)
 
    def tearDown(self):
        zope.component.testing.tearDown()
 
    def _constructDummyWorkflow(self):
 
        wftool = self.site.portal_workflow
        wftool._setObject('wf', SubtractiveWorkflowDefinition('wf'))
        wftool.setDefaultChain('wf')
        wf = wftool.wf
 
        wf.permissions = ('View',)
 
        wf.states.addState('nonconfidential')
        sdef = wf.states['nonconfidential']
        sdef.setProperties(transitions=('make_confidential',))
        sdef.permission_roles = {'View': (),}
 
        wf.states.addState('confidential')
        sdef = wf.states['confidential']
        sdef.setProperties(transitions=('make_non_confidential',))
        sdef.permission_roles = {'View': ('Anonymous', 'Authenticated',),}
 
        wf.states.setInitialState('confidential')
 
        wf.transitions.addTransition('make_confidential')
        tdef = wf.transitions['make_confidential']
        tdef.setProperties(title='', new_state_id='confidential', actbox_name='')
 
        wf.transitions.addTransition('make_non_confidential')
        tdef = wf.transitions['make_non_confidential']
        tdef.setProperties(title='', new_state_id='nonconfidential', actbox_name='')
 
        wf.variables.addVariable('comments')
        vdef = wf.variables['comments']
        vdef.setProperties(description='',
                 default_expr="python:state_change.kwargs.get('comment', '')",
                 for_status=1, update_always=1)
 
    def _getDummyWorkflow(self):
        wftool = self.site.portal_workflow
        return wftool.wf
 
    def test_doActionFor_mixture(self):
 
        wftool = self.site.portal_workflow
        wf = self._getDummyWorkflow()
 
        # This time, disallow Authenticated and Manager
        wf.states['confidential'].permission_roles = {'View': ('Authenticated', 'Manager'),}
 
        dummy = DummyContent() 
 
        # Now, if the item is normally granted to these roles
        dummy.manage_permission('View', ['Authenticated', 'Manager', 'Owner',], acquire=1)
 
        dummy = self.site._setObject('dummy', dummy)
 
        # These are the roles we know about
        self.assertEqual(['Anonymous', 'Authenticated', 'Manager', 'Owner'], sorted(dummy.validRoles()))
 
        self.assertEqual(wf._getStatusOf(dummy),
                         {'state': 'confidential', 'comments': ''})
 
        self.assertEquals(['Owner'], sorted(rolesForPermissionOn('View', dummy)))
 
    def test_doActionFor_anonymous(self):
 
        wftool = self.site.portal_workflow
        wf = self._getDummyWorkflow()
 
        dummy = DummyContent()
 
        # Now, if the item is normally granted to anonymous...
        dummy.manage_permission('View', ['Anonymous'], acquire=1)
 
        dummy = self.site._setObject('dummy', dummy)
 
        # These are the roles we know about
        self.assertEquals(['Anonymous', 'Authenticated', 'Manager', 'Owner'], sorted(dummy.validRoles()))
 
        self.assertEqual( wf._getStatusOf(dummy),
                          {'state': 'confidential', 'comments': ''} )
 
        self.assertEquals(['Manager', 'Owner'], sorted(rolesForPermissionOn('View', dummy)))
 
 
class SubtractiveWorkflowSecondaryTests(unittest.TestCase):
 
    layer = TraversingEventZCMLLayer
 
    def setUp(self):
        self.site = DummySite('site')
        self.site._setObject('portal_types', DummyTool())
        self.site._setObject('portal_workflow', WorkflowTool())
        self._constructDummyWorkflows()
 
        provideHandler(react.object_transitioned)
 
    def tearDown(self):
        # zope.component.testing.tearDown()
        pass
 
    def _constructDummyWorkflows(self):
 
        wftool = self.site.portal_workflow
        wftool._setObject('wf1', DCWorkflowDefinition('wf1'))
        wftool._setObject('wf2', SubtractiveWorkflowDefinition('wf2'))
 
        wftool.setDefaultChain('wf1, wf2')
        wf1 = wftool.wf1
        wf2 = wftool.wf2
 
        # First workflow - private/published
 
        wf1.permissions = ('View',)
 
        wf1.states.addState('private')
        sdef = wf1.states['private']
        sdef.setProperties(transitions=('publish',))
        sdef.permission_roles = {'View': ('Owner', 'Manager'),}
 
        wf1.states.addState('published')
        sdef = wf1.states['published']
        sdef.permission_roles = {'View': ('Anonymous',),}
 
        wf1.states.setInitialState('private')
 
        wf1.transitions.addTransition('publish')
        tdef = wf1.transitions['publish']
        tdef.setProperties(title='', new_state_id='published', actbox_name='')
 
        wf1.variables.addVariable('comments')
        vdef = wf1.variables['comments']
        vdef.setProperties(description='',
                 default_expr="python:state_change.kwargs.get('comment', '')",
                 for_status=1, update_always=1)
 
        # Second workflow - subtractive confidential/non-confidential
 
        wf2.permissions = ('View',)
 
        wf2.states.addState('nonconfidential')
        sdef = wf2.states['nonconfidential']
        sdef.setProperties(transitions=('make_confidential',))
        sdef.permission_roles = {'View': (),}
 
        wf2.states.addState('confidential')
        sdef = wf2.states['confidential']
        sdef.setProperties(transitions=('make_non_confidential',))
        sdef.permission_roles = {'View': ('Anonymous', 'Authenticated',),}
 
        wf2.states.setInitialState('nonconfidential')
 
        wf2.transitions.addTransition('make_confidential')
        tdef = wf2.transitions['make_confidential']
        tdef.setProperties(title='', new_state_id='confidential', actbox_name='')
 
        wf2.transitions.addTransition('make_non_confidential')
        tdef = wf2.transitions['make_non_confidential']
        tdef.setProperties(title='', new_state_id='nonconfidential', actbox_name='')
 
        wf2.variables.addVariable('comments')
        vdef = wf2.variables['comments']
        vdef.setProperties(description='',
                 default_expr="python:state_change.kwargs.get('comment', '')",
                 for_status=1, update_always=1)
 
    def _getDummyWorkflows(self):
        wftool = self.site.portal_workflow
        return (wftool.wf1, wftool.wf2,)
 
    def test_no_permissions_no_change(self):
 
        wftool = self.site.portal_workflow
        wf1, wf2 = self._getDummyWorkflows()
 
        dummy = self.site._setObject('dummy', DummyContent())
 
        # These are the roles we know about
        self.assertEquals(['Anonymous', 'Authenticated', 'Manager', 'Owner'], sorted(dummy.validRoles()))
 
        self.assertEqual(wf1._getStatusOf(dummy),
                         {'state': 'private', 'comments': ''})
        self.assertEqual(wf2._getStatusOf(dummy),
                         {'state': 'nonconfidential', 'comments': ''})
 
        # When private and non-confidential, manager and owner have the permission
        self.assertEquals(['Manager', 'Owner'], sorted(rolesForPermissionOn('View', dummy)))
 
        wftool.doActionFor(dummy, 'publish', comment='foo')
        self.assertEqual(wf1._getStatusOf(dummy),
                         {'state': 'published', 'comments': 'foo'})
        self.assertEqual(wf2._getStatusOf(dummy),
                         {'state': 'nonconfidential', 'comments': ''})
 
        # The item is now accessible to anonymous
        self.assertEquals(['Anonymous'], sorted(rolesForPermissionOn('View', dummy)))
 
    def test_with_permissions_subtracts(self):
 
        wftool = self.site.portal_workflow
        wf1, wf2 = self._getDummyWorkflows()
 
        dummy = self.site._setObject( 'dummy', DummyContent())
 
        # These are the roles we know about
        self.assertEquals(['Anonymous', 'Authenticated', 'Manager', 'Owner'], sorted(dummy.validRoles()))
 
        self.assertEqual(wf1._getStatusOf(dummy),
                         {'state': 'private', 'comments': ''})
        self.assertEqual(wf2._getStatusOf(dummy),
                         {'state': 'nonconfidential', 'comments': ''})
 
        # When private and non-confidential, manager and owner have the permission
        self.assertEquals(['Manager', 'Owner'], sorted(rolesForPermissionOn('View', dummy)))
 
        wftool.doActionFor(dummy, 'publish', comment='foo')
        self.assertEqual(wf1._getStatusOf(dummy),
                         {'state': 'published', 'comments': 'foo'})
        self.assertEqual(wf2._getStatusOf(dummy),
                         {'state': 'nonconfidential', 'comments': ''})
 
        # The item is now accessible to anonymous
        self.assertEquals(['Anonymous'], sorted(rolesForPermissionOn('View', dummy)))
 
        # Then let's make it confidential
        wftool.doActionFor(dummy, 'make_confidential', comment='bar')
        self.assertEqual(wf1._getStatusOf(dummy),
                         {'state': 'published', 'comments': 'foo'})
        self.assertEqual(wf2._getStatusOf(dummy),
                         {'state': 'confidential', 'comments': 'bar'})
 
        # The item is no longer accessible to roles that are being subtracted
        self.assertEquals(['Manager', 'Owner'], sorted(rolesForPermissionOn('View', dummy)))
 
    def test_with_permissions_reacts(self):
 
        wftool = self.site.portal_workflow
        wf1, wf2 = self._getDummyWorkflows()
 
        dummy = self.site._setObject('dummy', DummyContent())
        # setting the object will notify an AddedObjectEvent, which call
        # Products.CMFCore.CMFCatalogAware.handleContentishEvent which call
        # ob.notifyWorkflowCreated() which call wftool.notifyCreated(ob)
        # which notify AfterTransitionEvent for wf1, and then for wf2.
        # event.transition is None, but we want to execute the code of react.object_transitioned
        # in the case of confidential state as the initial state
 
        # These are the roles we know about
        self.assertEquals(['Anonymous', 'Authenticated', 'Manager', 'Owner'], sorted(dummy.validRoles()))
 
#        wftool.notifyCreated(dummy)
        self.assertEqual(wf1._getStatusOf(dummy),
                         {'state': 'private', 'comments': ''})
        self.assertEqual(wf2._getStatusOf(dummy),
                         {'state': 'nonconfidential', 'comments': ''})
 
        # When private and non-confidential, manager and owner have the permission
        self.assertEqual(['Manager', 'Owner'], sorted(rolesForPermissionOn('View', dummy)))
 
        # Let's make it confidential too
 
        wftool.doActionFor(dummy, 'make_confidential', comment='bar')
        self.assertEqual(wf1._getStatusOf(dummy),
                         {'state': 'private', 'comments': ''})
        self.assertEqual(wf2._getStatusOf(dummy),
                         {'state': 'confidential', 'comments': 'bar'})
 
        # If we now publish it, the result should be the same as if we'd
        # published first and then made
 
        wftool.doActionFor(dummy, 'publish', comment='foo')
        self.assertEqual(wf1._getStatusOf(dummy),
                         {'state': 'published', 'comments': 'foo'})
        self.assertEqual(wf2._getStatusOf(dummy),
                         {'state': 'confidential', 'comments': 'bar'})
 
        self.assertEquals(['Manager', 'Owner'], sorted(rolesForPermissionOn('View', dummy)))
 
    def test_undo(self):
 
        wftool = self.site.portal_workflow
        wf1, wf2 = self._getDummyWorkflows()
 
        dummy = self.site._setObject('dummy', DummyContent())
 
        # These are the roles we know about
        self.assertEquals(['Anonymous', 'Authenticated', 'Manager', 'Owner'], sorted(dummy.validRoles()))
 
        self.assertEqual(wf1._getStatusOf(dummy),
                         {'state': 'private', 'comments': ''})
        self.assertEqual(wf2._getStatusOf(dummy),
                         {'state': 'nonconfidential', 'comments': ''})
 
        # When private and non-confidential, manager and owner have the permission
        self.assertEquals(['Manager', 'Owner'], sorted(rolesForPermissionOn('View', dummy)))
 
        # Let's make it confidential too
 
        wftool.doActionFor(dummy, 'make_confidential', comment='bar')
        self.assertEqual(wf1._getStatusOf(dummy),
                         {'state': 'private', 'comments': ''})
        self.assertEqual(wf2._getStatusOf(dummy),
                         {'state': 'confidential', 'comments': 'bar'})
 
        # If we now publish it, the result should be the same as if we'd
        # published first and then made
 
        wftool.doActionFor(dummy, 'publish', comment='foo')
        self.assertEqual(wf1._getStatusOf(dummy),
                         {'state': 'published', 'comments': 'foo'} )
        self.assertEqual(wf2._getStatusOf(dummy),
                         {'state': 'confidential', 'comments': 'bar'})
 
        self.assertEquals(['Manager', 'Owner'], sorted(rolesForPermissionOn('View', dummy)))
 
        # Now let's make it non-confidential. The result should be the same
        # as if it was published
 
        wftool.doActionFor(dummy, 'make_non_confidential', comment='baz')
        self.assertEqual(wf1._getStatusOf(dummy),
                         {'state': 'published', 'comments': 'foo'})
        self.assertEqual(wf2._getStatusOf(dummy),
                         {'state': 'nonconfidential', 'comments': 'baz'})
 
        self.assertEqual(['Anonymous'], sorted(rolesForPermissionOn('View', dummy)))
 
 
class SubtractiveWorkflowSecondaryTestsConfidentialByDefault(unittest.TestCase):
 
    layer = TraversingEventZCMLLayer
 
    def setUp(self):
        self.site = DummySite('site')
        self.site._setObject('portal_types', DummyTool())
        self.site._setObject('portal_workflow', WorkflowTool())
        self._constructDummyWorkflows()
 
        provideHandler(react.object_transitioned)
 
    def tearDown(self):
        # zope.component.testing.tearDown()
        pass
 
    def _constructDummyWorkflows(self):
 
        wftool = self.site.portal_workflow
        wftool._setObject('wf1', DCWorkflowDefinition('wf1'))
        wftool._setObject('wf2', SubtractiveWorkflowDefinition('wf2'))
 
        wftool.setDefaultChain('wf1, wf2')
        wf1 = wftool.wf1
        wf2 = wftool.wf2
 
        # First workflow - private/published
 
        wf1.permissions = ('View',)
 
        wf1.states.addState('private')
        sdef = wf1.states['private']
        sdef.setProperties(transitions=('publish',))
        sdef.permission_roles = {'View': ('Owner', 'Manager'),}
 
        wf1.states.addState('published')
        sdef = wf1.states['published']
        sdef.permission_roles = {'View': ('Anonymous', 'Owner'),}
 
        wf1.states.setInitialState('private')
 
        wf1.transitions.addTransition('publish')
        tdef = wf1.transitions['publish']
        tdef.setProperties(title='', new_state_id='published', actbox_name='')
 
        wf1.variables.addVariable('comments')
        vdef = wf1.variables['comments']
        vdef.setProperties(description='',
                 default_expr="python:state_change.kwargs.get('comment', '')",
                 for_status=1, update_always=1)
 
        # Second workflow - subtractive confidential/non-confidential
 
        wf2.permissions = ('View',)
 
        wf2.states.addState('nonconfidential')
        sdef = wf2.states['nonconfidential']
        sdef.setProperties(transitions=('make_confidential',))
        sdef.permission_roles = {'View': (),}
 
        wf2.states.addState('confidential')
        sdef = wf2.states['confidential']
        sdef.setProperties(transitions=('make_non_confidential',))
        # Owner is removed when the doc is confidential, I actually wanted to remove
        # Member, that make more sense, but I have to pick an available role to test.
        sdef.permission_roles = {'View': ('Owner', 'Anonymous', 'Authenticated',),}
 
        wf2.states.setInitialState('confidential')
 
        wf2.transitions.addTransition('make_confidential')
        tdef = wf2.transitions['make_confidential']
        tdef.setProperties(title='', new_state_id='confidential', actbox_name='')
 
        wf2.transitions.addTransition('make_non_confidential')
        tdef = wf2.transitions['make_non_confidential']
        tdef.setProperties(title='', new_state_id='nonconfidential', actbox_name='')
 
        wf2.variables.addVariable('comments')
        vdef = wf2.variables['comments']
        vdef.setProperties(description='',
                 default_expr="python:state_change.kwargs.get('comment', '')",
                 for_status=1, update_always=1)
 
    def _getDummyWorkflows(self):
        wftool = self.site.portal_workflow
        return (wftool.wf1, wftool.wf2,)
 
    def test_with_permissions_subtracts(self):
 
        wftool = self.site.portal_workflow
        wf1, wf2 = self._getDummyWorkflows()
 
        dummy = self.site._setObject('dummy', DummyContent())
 
        # These are the roles we know about
        self.assertEquals(['Anonymous', 'Authenticated', 'Manager', 'Owner'], sorted(dummy.validRoles()))
 
        self.assertEqual(wf1._getStatusOf(dummy),
                         {'state': 'private', 'comments': ''})
        self.assertEqual(wf2._getStatusOf(dummy),
                         {'state': 'confidential', 'comments': ''})
 
        # When private and confidential, manager has the permission
        self.assertEquals(['Manager'], sorted(rolesForPermissionOn('View', dummy)))
 
        wftool.doActionFor(dummy, 'publish', comment='foo')
        self.assertEqual(wf1._getStatusOf(dummy),
                         {'state': 'published', 'comments': 'foo'})
        self.assertEqual(wf2._getStatusOf(dummy),
                         {'state': 'confidential', 'comments': ''})
 
        # The item is now accessible to manager
        self.assertEquals(['Manager'], sorted(rolesForPermissionOn('View', dummy)))
 
        wftool.doActionFor(dummy, 'make_non_confidential', comment='foo')
        self.assertEqual(wf1._getStatusOf(dummy),
                         {'state': 'published', 'comments': 'foo'})
        self.assertEqual(wf2._getStatusOf(dummy),
                         {'state': 'nonconfidential', 'comments': 'foo'})
 
        # The item is now accessible to anonymous and owner
        self.assertEquals(['Anonymous', 'Owner'], sorted(rolesForPermissionOn('View', dummy)))