import unittest
from datetime import datetime
from sets import Set

from zope.interface import implements
from zope.security.management import newInteraction, getInteraction,\
     endInteraction
from zope import event
from zope.security.interfaces import IGroupAwarePrincipal

from zope.app.event import objectevent

from zope.app import zapi
from zope.app.testing import functional
from zope.app.catalog.interfaces import ICatalog
from zope.app.intid.interfaces import IIntIds
from zope.app.mail.interfaces import IMailDelivery
from zope.app.component.hooks import setSite
from zope.app.container.interfaces import INameChooser
from zope.app.securitypolicy.interfaces import IPrincipalRoleManager
from zope.app.container.contained import Contained
from zope.app.authentication.interfaces import AuthenticatedPrincipalCreated

from hurry.workflow.interfaces import IWorkflowState, IWorkflowInfo

from documentlibrary.core import library, document, timezone
from documentlibrary.core import interfaces
from documentlibrary.core import category, flow, user, library
from documentlibrary.core.ipparse import IPRange

class DummyMailDelivery(Contained):
    implements(IMailDelivery)
    
    def send(self, fromaddr, toaddrs, message):
        pass # XXX perhaps track mail so we can verify in tests it's queued

class DocumentLibraryTestCase(functional.FunctionalTestCase):
    def setUp(self):
        super(DocumentLibraryTestCase, self).setUp()
        root = self.getRootFolder()
        self.app = library.DocumentLibrary()
        root['dl'] = self.app
        self._old_maildelivery = zapi.getUtility(
            IMailDelivery)
        library.addLocalUtility(self.app, u'',
                                IMailDelivery, DummyMailDelivery())
        
    def tearDown(self):
        library.addLocalUtility(self.app, u'',
                                IMailDelivery, self._old_maildelivery)
        setSite(None)
        super(DocumentLibraryTestCase, self).tearDown()

    def _addDocument(self, owner=None):
        cat = self.app['categories']['announcements']
        documents = cat['documents']
        intids = zapi.getUtility(IIntIds)
        cat_id = intids.getId(cat)
        if owner is not None:
            doc = document.Document(
                title='Test doc', category=cat_id, owner=owner,
                availabledate=timezone.nowInUTC(), expirydate=timezone.nowInUTC())
        else:
            doc = document.Document(
                title='Test doc', category=cat_id, 
                availabledate=timezone.nowInUTC(), expirydate=timezone.nowInUTC())
        event.notify(objectevent.ObjectCreatedEvent(doc))
        name = INameChooser(documents).chooseName('', doc)
        cat['documents'][name] = doc
        IWorkflowInfo(doc).fireTransition('submit')
        return doc
    
class TestDocumentCatalog(DocumentLibraryTestCase):

    def setUp(self):
        super(TestDocumentCatalog, self).setUp()
        # make some documents
        owners = ['Foo', 'Bar', 'Foo', 'Qux', 'Bar']
        for owner in owners:
            doc = self._addDocument(owner)
            
    def test_ids(self):
        ids = zapi.getUtility(IIntIds)
        c = 0
        for key, value in ids.items():
            if interfaces.IDocument.providedBy(value.object):
                c += 1
        self.assertEquals(5, c)

    def test_catalog1(self):
        catalog = zapi.getUtility(ICatalog, u'document_catalog')
        c = 0
        for result in catalog.searchResults(owner=('Foo', 'Foo')):
            c += 1
            self.assertEquals('Foo', result.owner)
        self.assertEquals(2, c)

    def test_catalog2(self):
        catalog = zapi.getUtility(ICatalog, u'document_catalog')
        c = 0
        for result in catalog.searchResults(owner=('Bar', 'Bar')):
            c += 1
            self.assertEquals('Bar', result.owner)
        self.assertEquals(2, c)     

    def test_catalog3(self):
        catalog = zapi.getUtility(ICatalog, u'document_catalog')
        c = 0
        for result in catalog.searchResults(owner=('Qux', 'Qux')):
            c += 1
            self.assertEquals('Qux', result.owner)
        self.assertEquals(1, c)
  
def documentsFromUntil(from_, until):
    catalog = zapi.getUtility(ICatalog, u'document_catalog')
    return catalog.searchResults(
        last_changed=(from_, until),
        workflow_state=(flow.APPROVED, flow.APPROVED))

class TestDocumentDateRange(DocumentLibraryTestCase):

    def setUp(self):
        super(TestDocumentDateRange, self).setUp()
        # make some documents, one per month
        documents = self.app['categories']['announcements']['documents']
        for month in range(1, 8):
            doc = document.Document()
            doc.last_changed = datetime(2005, month, 10)
            IWorkflowState(doc).setState(flow.APPROVED)
            name = INameChooser(documents).chooseName('', doc)
            documents[name] = doc
        # also make a bunch of non-approved documents
        for month in range(1, 8):
            doc = document.Document()
            doc.last_changed = datetime(2005, month, 11)
            IWorkflowState(doc).setState(flow.SUBMITTED)
            name = INameChooser(documents).chooseName('', doc)
            documents[name] = doc
        
    def test_daterange(self):
        result = documentsFromUntil(datetime(2005, 1, 1),
                                    datetime(2005, 8, 1))

        self.assertEquals(7, len(list(result)))

    def test_daterange_first_months(self):
        result = documentsFromUntil(datetime(2005, 1, 1),
                                    datetime(2005, 4, 4))
        self.assertEquals(3, len(list(result)))

    def test_daterange_only_from(self):
        result = documentsFromUntil(datetime(2005, 2, 1),
                                    None)
        self.assertEquals(6, len(list(result)))

    def test_daterange_only_until(self):
        result = documentsFromUntil(None,
                                    datetime(2005, 3, 1))
        self.assertEquals(2, len(list(result)))

    def test_daterange_all(self):
        result = documentsFromUntil(None, None)
        self.assertEquals(7, len(list(result)))

class Auth:
    prefix = ''

class TestDocumentOwnership(DocumentLibraryTestCase):

    def setUp(self):
        super(TestDocumentOwnership, self).setUp()
        users = self.app['users']
        users['bob'] =  user.UserInfo('bob', 'Bob',
                                      interfaces.SUBMITTER,
                                      'Bob', 'bob@infrae.com', 'Bob', [])
        users['joe'] = user.UserInfo('joe', 'Joe',
                                     interfaces.SUBMITTER,
                                     'Joe', 'joe@infrae.com', 'Joe', [])
        users['jane'] = user.UserInfo('jane', 'Jane',
                                      interfaces.VIEWER,
                                      'Jane', 'jane@infrae.com', 'Jane', [])
        users['carol'] = user.UserInfo('carol', 'Carol',
                                       interfaces.VIEWER,
                                       'Carol', 'carol@infrae.com',
                                       'Carol', [])
        users['lib'] = user.UserInfo('lib', 'lib',
                                     interfaces.LIBRARIAN,
                                     'Lib', 'lib@infrae.com',
                                     'Lib', [])
        
        groups = self.app['groups']
        groups['group1'] = user.GroupInfo('group1', 'Group1', '', ['jane'])
        groups['group2'] = user.GroupInfo('group2', 'Group2', '', ['carol'])
        groups['group3'] = user.GroupInfo('group3', 'Group3', '',
                                          ['jane', 'carol'])
        
        self.startInteraction('bob')
        
    def tearDown(self):
        super(TestDocumentOwnership, self).tearDown()
        self.endInteraction()

    def startInteraction(self, username):
        self.principal = principal = Principal('documentlibrary%s' % username)
        newInteraction(
            Participation(principal))
        event.notify(AuthenticatedPrincipalCreated(
            Auth(), principal, None, None))
        self.interaction = getInteraction()
        
    def endInteraction(self):
        endInteraction()
        
    def test_create_document(self):
        doc = self._addDocument()
        self.assertEquals('documentlibrarybob', doc.owner)
        
    def test_author(self):
        # make a test category where bob and bob have permission to add
        # documents
        librarians = Set()
        submitters = Set(['bob', 'joe'])
        cat = self.app['categories']['testcategory'] = category.Category(
            'foo', 0, librarians, submitters)
        documents = cat['documents']
        # now add a document with author using the email address of bob
        intids = zapi.getUtility(IIntIds)
        cat_id = intids.getId(cat)
        doc = document.Document(
            category=cat_id,
            authors=[('Bob', 'Bobson', 'bob@infrae.com')],
            availabledate=timezone.nowInUTC(),
            expirydate=timezone.nowInUTC())
        event.notify(objectevent.ObjectCreatedEvent(doc))
        name = INameChooser(documents).chooseName('', doc)
        documents[name] = doc
        IWorkflowInfo(doc).fireTransition('submit')

        self.assertEquals(
            True,
            self.interaction.checkPermission(
            'documentlibrary.EditDocument',
            doc))
        # now remove access
        doc.authors = []
        event.notify(objectevent.ObjectModifiedEvent(doc))
        self.assertEquals(
            False,
            self.interaction.checkPermission(
            'documentlibrary.EditDocument',
            doc))
        # now give both bob and joe access
        doc.authors = [('Bob', 'Bobson', 'bob@infrae.com'),
                       ('Joe', 'Joeson', 'joe@infrae.com')]
        event.notify(objectevent.ObjectModifiedEvent(doc))
        self.assertEquals(
            True,
            self.interaction.checkPermission(
            'documentlibrary.EditDocument',
            doc))
        self.endInteraction()
        self.startInteraction('joe')
        self.assertEquals(
            True,
            self.interaction.checkPermission(
            'documentlibrary.EditDocument',
            doc))
        self.endInteraction()
        self.startInteraction('bob')
        # now change email address of bob so he doesn't have access
        # anymore
        self.app['users']['bob'].email = 'bob2@infrae.com'
        self.assertEquals(
            False,
            self.interaction.checkPermission(
            'documentlibrary.EditDocument',
            doc))
        # change it back again so he does
        self.app['users']['bob'].email = 'bob@infrae.com'
        self.assertEquals(
            True,
            self.interaction.checkPermission(
            'documentlibrary.EditDocument',
            doc))
        # change it to a different case spelling; shouldn't matter
        self.app['users']['bob'].email = 'Bob@infrae.com'
        self.assertEquals(
            True,
            self.interaction.checkPermission(
            'documentlibrary.EditDocument',
            doc))
        # change listed email address on document to a different case
        # spelling; shouldn't matter either
        doc.authors = [('Bob', 'Bobson', 'boB@infrae.com'),
                       ('Joe', 'Joeson', 'joe@infrae.com')]
        self.assertEquals(
            True,
            self.interaction.checkPermission(
            'documentlibrary.EditDocument',
            doc))
        
    def test_viewer(self):
        librarians = Set(['lib'])
        submitters = Set(['bob', 'joe'])
        cat = self.app['categories']['testcategory'] = category.Category(
            'foo', 0, librarians, submitters)
        interfaces.ICategoryRoleManager(cat).addRoles(librarians, submitters)
        self.endInteraction()
        self.startInteraction('lib')
        self.assertEquals(
            True,
            self.interaction.checkPermission(
            'documentlibrary.ApproveDocument',
            cat))
        self.endInteraction()
        self.startInteraction('bob')
        documents = cat['documents']
        intids = zapi.getUtility(IIntIds)
        cat_id = intids.getId(cat)
        doc = document.Document(
            category=cat_id, access=['group1'],
            availabledate=timezone.nowInUTC(),
            expirydate=timezone.nowInUTC())
        event.notify(objectevent.ObjectCreatedEvent(doc))
        name = INameChooser(documents).chooseName('', doc)
        documents[name] = doc
        IWorkflowInfo(doc).fireTransition('submit')
        # bob can always view, as this is the author
        self.assertEquals(
            True,
            self.interaction.checkPermission(
            'documentlibrary.ViewDocument',
            doc))
        # however, jane can't view
        self.endInteraction()
        self.startInteraction('jane')
        self.assertEquals(
            False,
            self.interaction.checkPermission(
            'documentlibrary.ViewDocument',
            doc))
        self.endInteraction()
        self.startInteraction('lib')
        # now make document available
        IWorkflowInfo(doc).fireTransition('approve')
        IWorkflowInfo(doc).fireTransition('available')
        self.endInteraction()
        self.startInteraction('bob')
        # bob can still view
        self.assertEquals(
            True,
            self.interaction.checkPermission(
            'documentlibrary.ViewDocument',
            doc))
        # jane can view too
        self.endInteraction()
        self.startInteraction('jane')
        self.assertEquals(
            True,
            self.interaction.checkPermission(
            'documentlibrary.ViewDocument',
            doc))
        # but carol can't
        self.endInteraction()
        self.startInteraction('carol')
        self.assertEquals(
            False,
            self.interaction.checkPermission(
            'documentlibrary.ViewDocument',
            doc))
        self.endInteraction()
        self.startInteraction('bob')
        # we now make a new version of the document
        doc2 = IWorkflowInfo(doc).fireTransition('replace_from_available')
        
        # now we give carol access through group2
        doc2.access = ['group2']

        self.endInteraction()
        # carol can't view yet
        self.startInteraction('carol')
        self.assertEquals(
            False,
            self.interaction.checkPermission(
            'documentlibrary.ViewDocument',
            doc2))
        self.endInteraction()
        self.startInteraction('lib')
        # now make doc2 available
        IWorkflowInfo(doc2).fireTransition('approve')
        IWorkflowInfo(doc2).fireTransition('available')
        self.endInteraction()
        self.startInteraction('carol')
        # carol should be able to view now
        self.assertEquals(
            True,
            self.interaction.checkPermission(
            'documentlibrary.ViewDocument',
            doc2))
        # and jane can't
        self.endInteraction()
        self.startInteraction('jane')
        self.assertEquals(
            False,
            self.interaction.checkPermission(
            'documentlibrary.ViewDocument',
            doc2))
        # and jane also can't view now expired document anymore
        self.assertEquals(
            False,
            self.interaction.checkPermission(
            'documentlibrary.ViewDocument',
            doc))
        self.endInteraction()
        self.startInteraction('lib')
        # make another version
        # we now make a new version of the document
        doc3 = IWorkflowInfo(doc2).fireTransition('replace_from_available')
        
        # and now we give both jane and carol access through group3
        doc3.access = ['group2', 'group3']
        IWorkflowInfo(doc3).fireTransition('approve')
        IWorkflowInfo(doc3).fireTransition('available')  
        self.endInteraction()
        # jane has access
        self.startInteraction('jane')
        self.assertEquals(
            True,
            self.interaction.checkPermission(
            'documentlibrary.ViewDocument',
            doc3))
        self.endInteraction()
        # and carol
        self.startInteraction('carol')
        self.assertEquals(
            True,
            self.interaction.checkPermission(
            'documentlibrary.ViewDocument',
            doc3))

class TestResponsibleDocumentUsers(DocumentLibraryTestCase):

    def setUp(self):
        super(TestResponsibleDocumentUsers, self).setUp()
        users = self.app['users']
        users['bob'] =  user.UserInfo(
            'bob', 'Bob', interfaces.SUBMITTER, 'Bob', 'bob@infrae.com', 'Bob', [])
        users['joe'] = user.UserInfo(
            'joe', 'Joe', interfaces.SUBMITTER, 'Joe', 'joe@infrae.com', 'Joe', [])
        users['libby'] = user.UserInfo(
            'libby', 'libbby', interfaces.LIBRARIAN, 'Libby', 'lib@infrae.com', 'Libby', [])
        users['lubby'] = user.UserInfo(
            'lubby', 'lubbby', interfaces.LIBRARIAN, 'Lubby', 'lub@infrae.com', 'Lubby', [])
        cat1 = self.app['categories']['testcategory'] = category.Category(
            'foo', 0, Set(['libby']), Set([]))
        interfaces.ICategoryRoleManager(cat1).setRoles(['libby'], [])
        cat2 = cat1['subcategory'] = category.Category(
            'bar', 0, Set(['lubby']), Set([]))
        interfaces.ICategoryRoleManager(cat2).setRoles(['lubby'], [])
        documents = cat2['documents']
        # now add a document with author using the email address
        # of bob and joe
        intids = zapi.getUtility(IIntIds)
        cat_id = intids.getId(cat2)
        self.doc = doc = document.Document(
            category=cat_id,
            authors=[
                ('Bob', 'Bobson', 'bob@infrae.com'), 
                ('Joe', 'Johnson', 'joe@infrae.com')],
            availabledate=timezone.nowInUTC(),
            expirydate=timezone.nowInUTC())
        event.notify(objectevent.ObjectCreatedEvent(doc))
        name = INameChooser(documents).chooseName('', doc)
        documents[name] = doc
        IWorkflowInfo(doc).fireTransition('submit')
        
    def test_get_authors(self):
        responsible = interfaces.IResponsibleDocumentUsers(self.doc)
        expected = ['bob', 'joe']
        self.assertEquals(expected, responsible.getAuthors())

    def test_get_librarians(self):
        responsible = interfaces.IResponsibleDocumentUsers(self.doc)
        expected = ['libby', 'lubby']
        self.assertEquals(expected, responsible.getLibrarians())

    def test_get_nearest_librarians(self):
        responsible = interfaces.IResponsibleDocumentUsers(self.doc)
        self.assertEquals('lubby', responsible.getNearestLibrarian())
        
    def test_get_all_responsible(self):
        responsible = interfaces.IResponsibleDocumentUsers(self.doc)
        expected = ['libby', 'lubby'] + ['bob', 'joe']
        self.assertEquals(expected, responsible.getAllResponsible())
        
# some dummy stuff to make interactions work
class Principal:
    implements(IGroupAwarePrincipal)
    
    def __init__(self, id):
        self.id = id
        self.groups = []
    
from zope.i18n.format import DateTimeFormat
from zope.i18n.tests.test_formats import LocaleCalendarStub
class _dummyDates:
    
    def getFormatter(self, *args):
        return DateTimeFormat(
            pattern=u'dd.MM.yyyy HH:mm:ss', calendar=LocaleCalendarStub())
        
class _dummyLocale:
    dates = _dummyDates()
        
class Participation:
    interaction = None

    def __init__(self, principal, environ=None):
        self.principal = principal
        self.locale = _dummyLocale()
        self.environ = environ or {}
        
    def __getitem__(self, key):
        return self.environ[key]
    
class TestSecurity(DocumentLibraryTestCase):

    def setUp(self):
        super(TestSecurity, self).setUp()
        # need to use prefix for user
        newInteraction(Participation(Principal('documentlibrarybob')))
        self.interaction = getInteraction()
        
    def tearDown(self):
        endInteraction()
        super(TestSecurity, self).tearDown()
        
    def test_author(self):
        documents = self.app['categories']['announcements']['documents']
        self.assertEquals(
            False,
            self.interaction.checkPermission('documentlibrary.ContributeToCategory',
                                             documents)
            )
        # now we turn bob into an author in a new category
        librarians = Set()
        submitters = Set(['bob'])
        
        self.app['categories']['testcategory'] = category.Category(
            'foo', 0, librarians, submitters)
        cat = self.app['categories']['testcategory']
        interfaces.ICategoryRoleManager(cat).addRoles(librarians, submitters)
        # he should be able to add a document now in this category
        documents = cat['documents']
        self.assertEquals(
            True,
            self.interaction.checkPermission('documentlibrary.ContributeToCategory',
                                             documents))
        # now testcategory is edited so Bob loses permission
        librarians = Set()
        submitters = Set([])
        cat.librarians = librarians
        cat.submitters = submitters
        interfaces.ICategoryRoleManager(cat).setRoles(librarians, submitters)
        # and bob loses the ability to add documents here
        self.assertEquals(
            False,
            self.interaction.checkPermission('documentlibrary.ContributeToCategory',
                                             documents)
            )
        # now we edit testcategory again and give bob permission again
        librarians = Set()
        submitters = Set(['bob'])
        cat.librarians = librarians
        cat.submitters = submitters
        interfaces.ICategoryRoleManager(cat).setRoles(librarians, submitters)
        self.assertEquals(
            True,
            self.interaction.checkPermission('documentlibrary.ContributeToCategory',
                                             documents))
        
    def test_category_ownership(self):
        librarians = Set(['bob'])
        submitters = Set([])
        self.app['categories']['testcategory'] = category.Category(
            'foo', 0, librarians, submitters)
        cat = self.app['categories']['testcategory']
        interfaces.ICategoryRoleManager(cat).addRoles(librarians, submitters)
        # should now have ApproveDocument permission
        self.assertEquals(
            True,
            self.interaction.checkPermission('documentlibrary.ApproveDocument',
                                             cat))
        # now should see category show up
        categories = self.app['categories'].getOwnedCategories()
        self.assertEquals([cat], categories)
        # now remove ownership
        interfaces.ICategoryRoleManager(cat).setRoles(Set([]), Set([]))
        categories = self.app['categories'].getOwnedCategories()
        self.assertEquals([], categories)

    def test_owning_librarians(self):
        cat = self.app['categories']['testcategory'] = category.Category(
            'foo', 0, Set([]), Set([]))
        mgr = interfaces.ICategoryRoleManager(cat)
        self.assertEquals([], cat.getOwningLibrarians())
        mgr.setRoles(Set(['bob']), Set([]))
        self.assertEquals(['bob'], cat.getOwningLibrarians())
        # make subcategory
        cat2 = self.app['categories']['testcategory'][
            'sub'] = category.Category('bar', 0, Set([]), Set([]))
        self.assertEquals(
            ['bob'],
            cat2.getOwningLibrarians())

    def test_nearest_librarians(self):
        cat = self.app['categories']['testcategory'] = category.Category(
            'foo', 0, Set([]), Set([]))
        mgr = interfaces.ICategoryRoleManager(cat)
        self.assertEquals(None, cat.getNearestLibrarian())
        mgr.setRoles(Set(['bob']), Set([]))
        self.assertEquals('bob', cat.getNearestLibrarian())
        # make subcategory
        cat2 = self.app['categories']['testcategory'][
            'sub'] = category.Category('bar', 0, Set([]), Set([]))
        mgr = interfaces.ICategoryRoleManager(cat2)
        self.assertEquals('bob', cat2.getNearestLibrarian())
        mgr.setRoles(Set(['joe']), Set([]))
        self.assertEquals('joe', cat2.getNearestLibrarian())

class TestWorkflow(DocumentLibraryTestCase):

    def test_add_document(self):
        doc = self._addDocument()
 
        self.assertEquals(
            flow.SUBMITTED, IWorkflowState(doc).getState())
        self.assert_(IWorkflowState(doc).getId() is not None)
        self.assertEquals(
            ['approve', 'change_from_submitted', 'delete_from_submitted',
             'reject'],
            sorted(IWorkflowInfo(doc).getManualTransitionIds()))
        versions = list(flow.visibleVersions())
        self.assertEquals([doc], versions)

    def test_approve_available_document(self):
        doc = self._addDocument()
    
        IWorkflowInfo(doc).fireTransition('approve')
        self.assertEquals(
            flow.APPROVED, IWorkflowState(doc).getState())
        versions = list(flow.visibleVersions())
        self.assertEquals([doc], versions)
        IWorkflowInfo(doc).fireTransition('available')
        self.assertEquals(
            flow.AVAILABLE, IWorkflowState(doc).getState())
        self.assertEquals([doc], versions)
        new_doc = IWorkflowInfo(doc).fireTransition('replace_from_available')
        self.assertEquals(
            [new_doc], list(flow.visibleVersions()))
        self.assertEquals(
            flow.SUBMITTED, IWorkflowState(new_doc).getState())
        
class TestUser(DocumentLibraryTestCase):

    def test_add_user(self):
        users = self.app['users']
        users['foo'] = user.UserInfo('foo', 'foo',
                                     interfaces.SUBMITTER,
                                     'Foo', 'foo@infrae.com', 'Foo', [])
        manager = IPrincipalRoleManager(self.app)
        output = manager.getPrincipalsForRole('documentlibrary.SubmitterUser')
        self.assertEquals(1, len(output))
        principal_id, setting = output[0]
        self.assertEquals(
            'documentlibraryfoo',
            principal_id)

    def test_remove_user(self):
        users = self.app['users']
        # add it
        users['foo'] = user.UserInfo('foo', 'foo',
                                     interfaces.SUBMITTER,
                                     'Foo', 'foo@infrae.com', 'Foo', [])
        # remove it again
        del users['foo']
        manager = IPrincipalRoleManager(self.app)
        output = manager.getPrincipalsForRole('documentlibrary.SubmitterUser')
        self.assertEquals(0, len(output))

    def test_change_user(self):
        users = self.app['users']
        # add it
        users['foo'] = user.UserInfo('foo', 'foo',
                                     interfaces.SUBMITTER,
                                     'Foo', 'foo@infrae.com', 'Foo', [])
        users['foo'].role = 'Viewer'
        manager = IPrincipalRoleManager(self.app)
        output = manager.getPrincipalsForRole('documentlibrary.ViewerUser')
        self.assertEquals(1, len(output))
        
    def test_groups(self):
        groups = self.app['groups']
        groups['alpha'] = user.GroupInfo('alpha', 'Alpha', 'Alpha', [])
        users = self.app['users']
        foo = user.UserInfo('foo', 'foo',
                            interfaces.VIEWER,
                            'Foo', 'foo@infrae.com', 'Foo', [])
        foo.groups.add('alpha')
        users['foo'] = foo
        
        bar = user.UserInfo('bar', 'Bar',
                            interfaces.VIEWER,
                            'Bar', 'bar@infrae.com', 'Bar', [])
        bar.groups.add('alpha')
        users['bar'] = bar
        self.assertEquals(['bar', 'foo'],
                          sorted(groups['alpha'].users))


    def test_group_removal(self):
        groups = self.app['groups']
        groups['alpha'] = user.GroupInfo('alpha', 'Alpha', 'Alpha', [])
        users = self.app['users']
        foo = user.UserInfo('foo', 'foo',
                            interfaces.VIEWER,
                            'Foo', 'foo@infrae.com', 'Foo', [])
        foo.groups.add('alpha')
        users['foo'] = foo
        # now remove the group alpha
        del groups['alpha']
        # group should also be gone from user foo
        self.assertEquals([], list(foo.groups))
        
    def test_user_group_interaction(self):
        groups = self.app['groups']
        alpha = groups['alpha'] = user.GroupInfo('alpha', 'Alpha', 'Alpha', [])
        beta = groups['beta'] = user.GroupInfo('beta', 'Beta', 'Beta', [])
        users = self.app['users']
        foo = users['foo'] = user.UserInfo('foo', 'foo',
                            interfaces.VIEWER,
                            'Foo', 'foo@infrae.com', 'Foo', [])
        bar = users['bar'] = user.UserInfo('bar', 'bar',
                            interfaces.VIEWER,
                            'Bar', 'bar@infrae.com', 'Bar', [])
        alpha.users = ['foo', 'bar']
        self.assertEquals(Set(['bar', 'foo']), alpha.users)
        self.assertEquals(Set(), beta.users)
        self.assertEquals(Set(['alpha']), foo.groups)
        self.assertEquals(Set(['alpha']), bar.groups)
        alpha.users = []
        self.assertEquals(Set(), alpha.users)
        self.assertEquals(Set(), beta.users)
        self.assertEquals(Set(), foo.groups)
        self.assertEquals(Set(), bar.groups)
        alpha.users = ['foo']
        self.assertEquals(Set(['foo']), alpha.users)
        self.assertEquals(Set(), beta.users)
        self.assertEquals(Set(['alpha']), foo.groups)
        self.assertEquals(Set(), bar.groups)
        beta.users = ['foo']
        self.assertEquals(Set(['foo']), alpha.users)
        self.assertEquals(Set(['foo']), beta.users)
        self.assertEquals(Set(['alpha', 'beta']), foo.groups)
        self.assertEquals(Set(), bar.groups)
        foo.groups = Set(['alpha', 'beta'])
        self.assertEquals(Set(['foo']), alpha.users)
        self.assertEquals(Set(['foo']), beta.users)
        self.assertEquals(Set(['alpha', 'beta']), foo.groups)
        self.assertEquals(Set(), bar.groups)
        alpha.users = ['foo', 'bar']
        beta.users = ['foo', 'bar']
        self.assertEquals(Set(['foo', 'bar']), alpha.users)
        self.assertEquals(Set(['foo', 'bar']), beta.users)
        self.assertEquals(Set(['alpha', 'beta']), foo.groups)
        self.assertEquals(Set(['alpha', 'beta']), bar.groups)

    def test_userlookup(self):
        users = self.app['users']
        foo = users['foo'] = user.UserInfo('foo', 'foo',
                            interfaces.VIEWER,
                            'Foo', 'foo@infrae.com', 'Foo', [])
        bar = users['bar'] = user.UserInfo('bar', 'bar',
                            interfaces.VIEWER,
                            'Bar', 'bar@infrae.com', 'Bar', [])
        lookup = zapi.getUtility(interfaces.IUserInfoLookup)
        self.assertEquals('foo',
                          lookup.getUserInfoForEmail('foo@infrae.com').login)
        self.assertEquals('bar',
                          lookup.getUserInfoForEmail('bar@infrae.com').login)
        self.assertEquals(None,
                          lookup.getUserInfoForEmail('qux@infrae.com'))
                          
class TestHistory(DocumentLibraryTestCase):

    def _submit(self):
        announcements = self.app['categories']['announcements']
        documents = announcements['documents']
        category_id = zapi.getUtility(IIntIds).getId(announcements)
        doc = document.Document()
        doc.title = u'document'
        doc.owner = 'foo'
        doc.category = category_id
        name = INameChooser(documents).chooseName('', doc)
        documents[name] = doc
        IWorkflowInfo(doc).fireTransition('submit')
        return doc
    
    def test_submit(self):
        self._submit()
        history = self.app['history']
        self.assertEquals(1, len(history))
        self.assertEquals(None,
                          history.values()[0].source_state)
        self.assertEquals(flow.SUBMITTED,
                          history.values()[0].destination_state)
        
    def test_approve(self):
        doc = self._submit()
        IWorkflowInfo(doc).fireTransition('approve')
        history = self.app['history']
        self.assertEquals(2, len(history))
        self.assertEquals(flow.SUBMITTED,
                          history.values()[-1].source_state)
        self.assertEquals(flow.APPROVED,
                          history.values()[-1].destination_state)
        
    def test_reject(self):
        doc = self._submit()
        IWorkflowInfo(doc).fireTransition('reject')
        history = self.app['history']
        self.assertEquals(2, len(history))
        self.assertEquals(flow.SUBMITTED,
                          history.values()[-1].source_state)
        self.assertEquals(flow.REJECTED,
                          history.values()[-1].destination_state)

    def test_replace(self):
        doc = self._submit()
        IWorkflowInfo(doc).fireTransition('approve')
        IWorkflowInfo(doc).fireTransition('available')
        doc = IWorkflowInfo(doc).fireTransition('replace_from_available')
        
        history = self.app['history']
        self.assertEquals(4, len(history))
        self.assertEquals(flow.AVAILABLE,
                          history.values()[-1].source_state)
        self.assertEquals(flow.SUBMITTED,
                          history.values()[-1].destination_state)

class TestFilenameValidation(DocumentLibraryTestCase):
    def test_extensions(self):
        v = zapi.getUtility(interfaces.IFilenameValidation)
        
        v.forbidden_extensions = '.exe .com'
    
        v.checkFilename('foo.xml')
        self.assertRaises(
            interfaces.ForbiddenFilenameError,
            v.checkFilename, 'test.exe')

    def test_spaces(self):
        v = zapi.getUtility(interfaces.IFilenameValidation)
        v.checkFilename(r'foo\bar boo\baz')
        v.checkFilename('foo/bar boo/baz')
        
        self.assertRaises(
            interfaces.ForbiddenFilenameError,
            v.checkFilename, 'foo bar')

        # now allow them
        v.forbidden_spaces = False
        v.checkFilename('foo bar')

class TestIpGroupDownload(DocumentLibraryTestCase):
    def startInteraction(self, username, ip):
        self.principal = principal = Principal('documentlibrary%s' % username)
        participation = Participation(principal, {'REMOTE_ADDR': ip})
        newInteraction(participation)
        self.interaction = getInteraction()

    def endInteraction(self):
        endInteraction()
        
    def test_ipgroup(self):
        groups = self.app['ipgroups']
        groups['here'] = user.IpGroupInfo('here', 'Here', '',
                                          [IPRange('127.0.0.1')])
        cat = self.app['categories']['testcategory'] = category.Category(
            'foo', 0, [], [])
        documents = cat['documents']
        intids = zapi.getUtility(IIntIds)
        cat_id = intids.getId(cat)
        doc = document.Document(
            category=cat_id, access=['here'],
            availabledate=timezone.nowInUTC(),
            expirydate=timezone.nowInUTC())
        event.notify(objectevent.ObjectCreatedEvent(doc))
        name = INameChooser(documents).chooseName('', doc)
        documents[name] = doc

        IWorkflowInfo(doc).fireTransition('submit')
        IWorkflowInfo(doc).fireTransition('approve')
        IWorkflowInfo(doc).fireTransition('available')

        self.startInteraction('bob', '127.0.0.1')
        # Bob should be able to view due to IP address
        self.assertEquals(
            True,
            self.interaction.checkPermission(
            'documentlibrary.ViewDocument',
            doc))
        self.endInteraction()
        self.startInteraction('jane', '192.168.1.100')
        # Jane shouldn't be able to
        self.assertEquals(
            False,
            self.interaction.checkPermission(
            'documentlibrary.ViewDocument',
            doc))
        self.endInteraction()
        
def test_suite():
    return unittest.TestSuite([
        unittest.makeSuite(TestDocumentCatalog),
        unittest.makeSuite(TestDocumentDateRange),
        unittest.makeSuite(TestDocumentOwnership),
        unittest.makeSuite(TestResponsibleDocumentUsers),
        unittest.makeSuite(TestSecurity),
        unittest.makeSuite(TestWorkflow),
        unittest.makeSuite(TestUser),
        unittest.makeSuite(TestHistory),
        unittest.makeSuite(TestFilenameValidation),
        unittest.makeSuite(TestIpGroupDownload),
        ])
