# -*- encoding: utf-8 -*-
from Acquisition import aq_base
from DateTime import DateTime
from OFS.ObjectManager import REPLACEABLE
from plone.app.testing import TEST_USER_ID
from plone.app.testing import TEST_USER_NAME
from plone.indexer.wrapper import IndexableObjectWrapper
from plone.uuid.interfaces import IAttributeUUID
from plone.uuid.interfaces import IUUID
from Products.CMFCore.permissions import AccessInactivePortalContent
from Products.CMFPlone.CatalogTool import CatalogTool
from Products.CMFPlone.CatalogTool import is_folderish
from Products.CMFPlone.tests import dummy
from Products.CMFPlone.tests.PloneTestCase import PloneTestCase
from Products.CMFPlone.tests.utils import folder_position
from zope.event import notify
from zope.interface import alsoProvides
from zope.lifecycleevent import ObjectCreatedEvent
from z3c.form.interfaces import IFormLayer
import transaction
import unittest
import zope.interface

user2 = 'u2'
group2 = 'g2'

base_content = ['Members', 'aggregator', 'aggregator',
                'events', 'news', TEST_USER_ID, 'front-page', 'doc']


class TestCatalogSetup(PloneTestCase):

    def afterSetUp(self):
        self.catalog = self.portal.portal_catalog

    def testSearchableTextIsZCTextIndex(self):
        # SearchableText index should be a ZCTextIndex
        itype = self.catalog.Indexes['SearchableText'].__class__.__name__
        self.assertEqual(itype, 'ZCTextIndex')

    def testDescriptionIsZCTextIndex(self):
        # Description index should be a ZCTextIndex
        itype = self.catalog.Indexes['Description'].__class__.__name__
        self.assertEqual(itype, 'ZCTextIndex')

    def testTitleIsZCTextIndex(self):
        # Title index should be a ZCTextIndex
        itype = self.catalog.Indexes['Title'].__class__.__name__
        self.assertEqual(itype, 'ZCTextIndex')

    def testPloneLexiconIsZCTextLexicon(self):
        # Lexicon should be a ZCTextIndex lexicon
        self.assertTrue(hasattr(aq_base(self.catalog), 'plone_lexicon'))
        self.assertEqual(self.catalog.plone_lexicon.meta_type,
                         'ZCTextIndex Lexicon')

    def testPathIsExtendedPathIndex(self):
        # path index should be an ExtendedPathIndex
        self.assertEqual(self.catalog.Indexes['path'].__class__.__name__,
                         'ExtendedPathIndex')

    def testGetObjPositionInParentIsGopipIndex(self):
        # getObjPositionInParent index should be a FieldIndex
        # also see TestCatalogOrdering below
        self.assertEqual(
            self.catalog.Indexes['getObjPositionInParent'].__class__.__name__,
            'GopipIndex')

    def testGetObjSizeInSchema(self):
        # getObjSize column should be in catalog schema
        self.assertTrue('getObjSize' in self.catalog.schema())

    def testExclude_from_navInSchema(self):
        # exclude_from_nav column should be in catalog schema
        self.assertTrue('exclude_from_nav' in self.catalog.schema())

    def testIs_folderishInSchema(self):
        # is_folderish should be in catalog schema
        self.assertTrue('is_folderish' in self.catalog.schema())

    def testIs_folderishIsBooleanIndex(self):
        # is_folderish should be a BooleanIndex
        self.assertTrue(
            self.catalog.Indexes['is_folderish'].__class__.__name__,
            'BooleanIndex')

    def testDateIsDateIndex(self):
        # Date should be a DateIndex
        self.assertEqual(self.catalog.Indexes['Date'].__class__.__name__,
                         'DateIndex')

    def testCreatedIsDateIndex(self):
        # created should be a DateIndex
        self.assertEqual(self.catalog.Indexes['created'].__class__.__name__,
                         'DateIndex')

    def testEffectiveIsDateIndex(self):
        # effective should be a DateIndex
        self.assertEqual(self.catalog.Indexes['effective'].__class__.__name__,
                         'DateIndex')

    def testEndIsDateIndex(self):
        # end should be a DateIndex
        self.assertEqual(self.catalog.Indexes['end'].__class__.__name__,
                         'DateIndex')

    def testExpiresIsDateIndex(self):
        # expires should be a DateIndex
        self.assertEqual(self.catalog.Indexes['expires'].__class__.__name__,
                         'DateIndex')

    def testModifiedIsDateIndex(self):
        # modified should be a DateIndex
        self.assertEqual(self.catalog.Indexes['modified'].__class__.__name__,
                         'DateIndex')

    def testStartIsDateIndex(self):
        # start should be a DateIndex
        self.assertEqual(self.catalog.Indexes['start'].__class__.__name__,
                         'DateIndex')

    def testEffectiveRangeIsDateRangeIndex(self):
        # effectiveRange should be a DateRangeIndex
        self.assertEqual(
            self.catalog.Indexes['effectiveRange'].__class__.__name__,
            'DateRangeIndex')

    def testSortable_TitleIsFieldIndex(self):
        # sortable_title should be a FieldIndex
        self.assertEqual(
            self.catalog.Indexes['sortable_title'].__class__.__name__,
            'FieldIndex')

    def testExpirationDateInSchema(self):
        # ExpirationDate column should be in catalog schema
        self.assertTrue('ExpirationDate' in self.catalog.schema())

    def testExpiresDateNotInSchema(self):
        # ExpirationDate column should be in catalog schema
        self.assertFalse('ExpiresDate' in self.catalog.schema())

    def testIs_Default_PageIsBooleanIndex(self):
        # sortable_title should be a BooleanIndex
        self.assertEqual(
            self.catalog.Indexes['is_default_page'].__class__.__name__,
            'BooleanIndex')


class TestCatalogIndexing(PloneTestCase):

    def afterSetUp(self):
        self.catalog = self.portal.portal_catalog
        self.folder.invokeFactory('Document', id='doc',
                                  title='Foo', description='Bar')
        self.catalog.unindexObject(self.folder.doc)

    def assertResults(self, result, expect):
        # Verifies ids of catalog results against expected ids
        lhs = [r.getId for r in result]
        lhs.sort()
        rhs = list(expect)
        rhs.sort()
        self.assertEqual(lhs, rhs)

    def testFixture(self):
        self.assertEqual(self.folder.doc.getId(), 'doc')
        self.assertEqual(self.folder.doc.Title(), 'Foo')
        self.assertEqual(self.folder.doc.Description(), 'Bar')
        self.assertEqual(len(self.catalog(getId='doc')), 0)
        self.assertEqual(len(self.catalog(Title='Foo')), 0)
        self.assertEqual(len(self.catalog(Description='Bar')), 0)

    def testIndexObject(self):
        # Object should be indexed
        self.catalog.indexObject(self.folder.doc)
        self.assertEqual(len(self.catalog(getId='doc')), 1)
        self.assertEqual(len(self.catalog(Title='Foo')), 1)
        self.assertEqual(len(self.catalog(Description='Bar')), 1)

    def testReindexObject(self):
        # Object should be indexed
        self.catalog.reindexObject(self.folder.doc)
        self.assertEqual(len(self.catalog(getId='doc')), 1)
        self.assertEqual(len(self.catalog(Title='Foo')), 1)
        self.assertEqual(len(self.catalog(Description='Bar')), 1)

    def testUnindexObject(self):
        # Object should be unindexed
        self.catalog.indexObject(self.folder.doc)
        self.assertEqual(len(self.catalog(getId='doc')), 1)
        self.catalog.unindexObject(self.folder.doc)
        self.assertEqual(len(self.catalog(getId='doc')), 0)

    def testIndexObjectUpdatesMetadata(self):
        # Indexing should update metadata
        self.catalog.indexObject(self.folder.doc)
        brain = self.catalog(getId='doc')[0]
        self.assertEqual(brain.getId, 'doc')
        self.assertEqual(brain.Title, 'Foo')
        self.assertEqual(brain.Description, 'Bar')

    def testReindexObjectUpdatesMetadata(self):
        # Reindexing should update metadata
        self.catalog.indexObject(self.folder.doc)
        self.folder.doc.setTitle('Fred')
        self.folder.doc.setDescription('BamBam')
        self.catalog.reindexObject(self.folder.doc)
        brain = self.catalog(getId='doc')[0]
        self.assertEqual(brain.getId, 'doc')
        self.assertEqual(brain.Title, 'Fred')
        self.assertEqual(brain.Description, 'BamBam')

    def testReindexObjectSkipsMetadata(self):
        # Reindexing should not update metadata when update_metadata=0
        self.catalog.indexObject(self.folder.doc)
        self.folder.doc.setTitle('Fred')
        self.folder.doc.setDescription('BamBam')
        self.catalog.reindexObject(self.folder.doc, update_metadata=0)
        brain = self.catalog(getId='doc')[0]
        # Metadata did not change
        self.assertEqual(brain.getId, 'doc')
        self.assertEqual(brain.Title, 'Foo')
        self.assertEqual(brain.Description, 'Bar')

    def testReindexTitleOnly(self):
        # Reindexing should only index the Title
        self.catalog.indexObject(self.folder.doc)
        self.folder.doc.setTitle('Fred')
        self.folder.doc.setDescription('BamBam')
        self.catalog.reindexObject(self.folder.doc, idxs=['Title'])
        self.assertEqual(len(self.catalog(getId='doc')), 1)
        self.assertEqual(len(self.catalog(Title='Fred')), 1)
        # Description index did not change
        self.assertEqual(len(self.catalog(Description='Bar')), 1)
        self.assertEqual(len(self.catalog(Description='BamBam')), 0)

    def testReindexTitleOnlyUpdatesMetadata(self):
        # Reindexing Title should update metadata
        self.catalog.indexObject(self.folder.doc)
        self.folder.doc.setTitle('Fred')
        self.folder.doc.setDescription('BamBam')
        self.catalog.reindexObject(self.folder.doc, idxs=['Title'])
        brain = self.catalog(getId='doc')[0]
        self.assertEqual(brain.getId, 'doc')
        self.assertEqual(brain.Title, 'Fred')
        self.assertEqual(brain.Description, 'BamBam')

    def testReindexTitleOnlySkipsMetadata(self):
        # Reindexing Title should not update metadata when update_metadata=0
        self.catalog.indexObject(self.folder.doc)
        self.folder.doc.setTitle('Fred')
        self.folder.doc.setDescription('BamBam')
        self.catalog.reindexObject(self.folder.doc, idxs=['Title'],
                                   update_metadata=0)
        brain = self.catalog(getId='doc')[0]
        # Metadata did not change
        self.assertEqual(brain.getId, 'doc')
        self.assertEqual(brain.Title, 'Foo')
        self.assertEqual(brain.Description, 'Bar')

    def testIndexTitleOnly(self):
        # Indexing should only index the Title
        #
        # TODO: This does not work as expected. The object
        # appears to be in the catalog but is not returned
        # by searchResults()!?
        #
        self.catalog.indexObject(self.folder.doc, idxs=['Title'])
        # The document is cataloged
        path = self.catalog._CatalogTool__url(self.folder.doc)
        self.assertTrue(path in self.catalog._catalog.paths.values())
        # But it is not returned when searching...
        self.assertEqual(len(self.catalog(getId='doc')), 0)
        self.assertEqual(len(self.catalog(Title='Foo')), 0)  # <-- Should be 1
        self.assertEqual(len(self.catalog(Description='Bar')), 0)

    def testIndexIdOnly(self):
        # Indexing should only index the id
        #
        # TODO: Demonstrate that the behavior is independent
        # of index type.
        #
        self.catalog.indexObject(self.folder.doc, idxs=['getId'])
        # The document is cataloged
        path = self.catalog._CatalogTool__url(self.folder.doc)
        self.assertTrue(path in self.catalog._catalog.paths.values())
        # But it is not returned when searching...
        self.assertEqual(len(self.catalog(getId='doc')), 0)  # <-- Should be 1
        self.assertEqual(len(self.catalog(Title='Foo')), 0)
        self.assertEqual(len(self.catalog(Description='Bar')), 0)

    def testClearFindAndRebuildRemovesBadContent(self):
        # Index the doc for consistency
        self.catalog.indexObject(self.folder.doc)
        res = self.catalog.searchResults()
        self.assertResults(res, base_content)
        # index an object which shouldn't be there
        self.catalog.indexObject(self.portal.portal_skins)
        res = self.catalog.searchResults()
        # Since the introduction of the IIndexableObject interface, we cannot
        # easily get bad content into the catalog anymore
        self.assertResults(res, base_content)
        self.catalog.clearFindAndRebuild()
        # This will add the document added in afterSetup
        res = self.catalog.searchResults()
        self.assertResults(res, base_content)

    def testClearFindAndRebuildAddsMissingContent(self):
        # Index the doc for consistency
        self.catalog.indexObject(self.folder.doc)
        res = self.catalog.searchResults()
        self.assertResults(res, base_content)
        # index an object which shouldn't be there
        self.catalog.unindexObject(self.portal.Members)
        altered_content = base_content[:]
        altered_content.remove('Members')
        res = self.catalog.searchResults()
        self.assertResults(res, altered_content)
        self.catalog.clearFindAndRebuild()
        # This will add the missing item and also the document added
        # in afterSetup
        res = self.catalog.searchResults()
        self.assertResults(res, base_content)

    def testClearFindAndRebuildKeepsModificationDate(self):
        # Index the doc for consistency
        self.catalog.indexObject(self.folder.doc)
        self.folder.doc.setModificationDate(DateTime(0))
        self.catalog.clearFindAndRebuild()
        self.assertEqual(self.folder.doc.modified(), DateTime(0))
        self.assertEqual(len(self.catalog(modified=DateTime(0))), 1)


class TestCatalogSearching(PloneTestCase):

    def afterSetUp(self):
        self.catalog = self.portal.portal_catalog
        self.workflow = self.portal.portal_workflow
        self.groups = self.portal.portal_groups

        self.portal.acl_users._doAddUser(user2, 'secret', [], [])

        self.folder.invokeFactory('Document', id='doc', text='foo')
        self.folder.invokeFactory('Folder', id='folder2')
        self.folder.folder2.invokeFactory('Document', id='doc2', text='bar')
        self.workflow.doActionFor(self.folder.doc, 'hide', comment='')
        self.workflow.doActionFor(self.folder.folder2, 'hide', comment='')
        self.workflow.doActionFor(self.folder.folder2.doc2, 'hide', comment='')

        # Used for testing AND/OR search functionality below
        self.folder.invokeFactory('Document', id='aaa', text='aaa',
                                  title='ccc')
        self.folder.invokeFactory('Document', id='bbb', text='bbb')

        self.setupAuthenticator()

    def addUser2ToGroup(self):
        self.groups.groupWorkspacesCreationFlag = 0
        self.groups.addGroup(group2, None, [], [])
        group = self.groups.getGroupById(group2)
        self.loginAsPortalOwner()  # GRUF 3.52
        group.addMember(user2)
        self.login(TEST_USER_NAME)  # Back to normal
        return group2

    def testListAllowedRolesAndUsers(self):
        # Should include the group in list of allowed users
        groupname = self.addUser2ToGroup()
        uf = self.portal.acl_users
        user = 'user:{0:s}'.format(groupname)
        self.assertTrue(
            user in self.catalog._listAllowedRolesAndUsers(uf.getUser(user2)))

    def testSearchReturnsDocument(self):
        # Document should be found when owner does a search
        self.assertEqual(self.catalog(SearchableText='aaa')[0].id, 'aaa')

    def testSearchDoesNotReturnDocument(self):
        # Document should not be found when user2 does a search
        self.login(user2)
        self.assertEqual(len(self.catalog(SearchableText='foo')), 0)

    def testSearchReturnsDocumentUsing_DefaultAND(self):
        # Documents should not be found when searching 'aaa bbb' (which should
        # default to AND)
        self.assertEqual(len(self.catalog(SearchableText='aaa bbb')), 0)
        self.assertEqual(len(self.catalog(SearchableText='aaa ccc')), 1)

    def testSearchReturnsDocumentUsing_AND(self):
        # Documents should not be found when owner does a search using AND
        self.assertEqual(len(self.catalog(SearchableText='aaa AND bbb')), 0)
        self.assertEqual(len(self.catalog(SearchableText='aaa AND ccc')), 1)

    def testSearchReturnsDocumentUsing_OR(self):
        # Two documents (aaa, bbb)  should be found when owner does a search
        # using OR
        results = self.catalog(SearchableText='aaa OR bbb')
        self.assertEqual(len(results), 2)

    def testSearchIgnoresAccents(self):
        #plip 12110
        self.folder.invokeFactory(
            'Document', id='docwithaccents1', description='Econométrie')
        self.folder.invokeFactory(
            'Document', id='docwithaccents2', description='ECONOMETRIE')
        self.folder.invokeFactory(
            'Document', id='docwithaccents3', description='économétrie')
        self.folder.invokeFactory(
            'Document', id='docwithaccents4', description='ÉCONOMÉTRIE')

        self.assertEqual(len(self.catalog(SearchableText='econometrie')), 4)
        self.assertEqual(len(self.catalog(SearchableText='économétrie')), 4)
        self.assertEqual(len(self.catalog(SearchableText='Econométrie')), 4)
        self.assertEqual(len(self.catalog(SearchableText='ÉCONOMÉTRIE')), 4)

        self.assertEqual(len(self.catalog(SearchableText='econom?trie')), 4)
        self.assertEqual(len(self.catalog(SearchableText='econometr*')), 4)

        # non-regression with eastern language
        # (use plone.i18n ja normalizer test)
        self.folder.invokeFactory(
            'Document', id='docwithjapanchars', description="テストページ")
        self.assertEqual(len(self.catalog(SearchableText="テストページ")), 1)

        # test with language specific char (fr)
        self.folder.invokeFactory(
            'Document', id='docwithfrenchlatinchar', description='œuf')
        self.assertEqual(len(self.catalog(SearchableText='œuf')), 1)
        self.assertEqual(len(self.catalog(SearchableText='oeuf')), 1)
        self.assertEqual(len(self.catalog(SearchableText='Œuf')), 1)
        self.assertEqual(len(self.catalog(SearchableText='OEUF')), 1)
        self.assertEqual(len(self.catalog(SearchableText='uf')), 0)

    def testSearchReturnsDocumentWhenPermissionIsTroughLocalRole(self):
        # After adding a group with access rights and containing user2,
        # a search must find the document.
        groupname = self.addUser2ToGroup()
        sharingView = self.folder.unrestrictedTraverse('@@sharing')
        sharingView.update_role_settings([{'id': groupname,
                                           'type': 'group',
                                           'roles': ['Owner']}])
        self.login(user2)
        self.assertEqual(self.catalog(SearchableText='aaa')[0].id, 'aaa')

    def testSearchRespectsLocalRoleAcquisition(self):
        # After adding a group with access rights and containing user2,
        # a search must find the document in subfolders.
        groupname = self.addUser2ToGroup()
        sharingView = self.folder.unrestrictedTraverse('@@sharing')
        sharingView.update_role_settings([{'id': groupname,
                                           'type': 'group',
                                           'roles': ['Owner']}])
        self.login(user2)
        # Local Role works in subfolder
        self.assertEqual(self.catalog(SearchableText='bbb')[0].id, 'bbb')

    def testSearchRespectsLocalRoleAcquisitionDisabled(self):
        # After adding a group with access rights and containing user2,
        # a search should not find documents in subfolders which have
        # disabled local role acquisition.
        groupname = self.addUser2ToGroup()
        sharingView = self.folder.unrestrictedTraverse('@@sharing')
        sharingView.update_role_settings([{'id': groupname,
                                           'type': 'group',
                                           'roles': ['Owner']}])
        # Acquisition off for folder2
        self.folder.folder2.unrestrictedTraverse('@@sharing') \
            .update_inherit(False)
        # Everything in subfolder should be invisible
        self.login(user2)
        self.assertFalse(self.catalog(SearchableText='bar'))

    def testSearchIgnoreAccents(self):
        """PLIP 12110
        """
        self.folder.invokeFactory(
            'Document', id='docwithaccents-1', text='Econométrie', title='foo')
        self.folder.invokeFactory(
            'Document', id='docwithaccents-2', text='Économétrie')
        self.folder.invokeFactory(
            'Document', id='docwithout-accents', text='ECONOMETRIE')

        self.assertEqual(len(self.catalog(SearchableText='Économétrie')), 3)
        self.assertEqual(len(self.catalog(SearchableText='Econométrie')), 3)
        self.assertEqual(len(self.catalog(SearchableText='ECONOMETRIE')), 3)

    def testSearchIsProtected(self):
        self.login()
        self.folder.invokeFactory("Document", "sekretz")
        self.logout()
        catalog = self.portal.portal_catalog
        bogus = catalog.search({'portal_type': 'Document'})
        real = catalog.portal_catalog.searchResults(portal_type='Document')
        self.assertEqual(len(bogus), len(real))


class TestCatalogSorting(PloneTestCase):

    def afterSetUp(self):
        self.catalog = self.portal.portal_catalog

        self.folder.invokeFactory('Document', id='doc', text='foo')
        self.folder.doc.setTitle('12 Document 25')
        self.folder.invokeFactory('Document', id='doc2', text='foo')
        self.folder.doc2.setTitle('3 Document 4')
        self.folder.invokeFactory('Document', id='doc3', text='foo')
        self.folder.doc3.setTitle('12 Document 4')

        self.folder.invokeFactory('Document', id='doc4', text='bar')
        self.folder.doc4.setTitle('document 12')
        self.folder.invokeFactory('Document', id='doc5', text='bar')
        self.folder.doc5.setTitle('Document 2')
        self.folder.invokeFactory('Document', id='doc6', text='bar')
        self.folder.doc6.setTitle('DOCUMENT 4')
        self.folder.doc.reindexObject()
        self.folder.doc2.reindexObject()
        self.folder.doc3.reindexObject()
        self.folder.doc4.reindexObject()
        self.folder.doc5.reindexObject()
        self.folder.doc6.reindexObject()

    def testSortTitleReturnsProperOrderForNumbers(self):
        # Documents should be returned in proper numeric order
        results = self.catalog(SearchableText='foo', sort_on='sortable_title')
        self.assertEqual(results[0].getId, 'doc2')
        self.assertEqual(results[1].getId, 'doc3')
        self.assertEqual(results[2].getId, 'doc')

    def testSortTitleIgnoresCase(self):
        # Documents should be returned in case insensitive order
        results = self.catalog(SearchableText='bar', sort_on='sortable_title')
        self.assertEqual(results[0].getId, 'doc5')
        self.assertEqual(results[1].getId, 'doc6')
        self.assertEqual(results[2].getId, 'doc4')

    def testSortableTitleOutput(self):
        doc = self.folder.doc
        wrapped = IndexableObjectWrapper(doc, self.portal.portal_catalog)

        self.assertEqual(wrapped.sortable_title, '0012 document 0025')

    def testSortableNonASCIITitles(self):
        #test a utf-8 encoded string gets properly unicode converted
        #sort must ignore accents
        title = 'La Pe\xc3\xb1a'
        doc = self.folder.doc
        doc.setTitle(title)
        wrapped = IndexableObjectWrapper(doc, self.portal.portal_catalog)
        self.assertEqual(wrapped.sortable_title, 'la pena')

    def testSortableDate(self):
        title = '2012-06-01 foo document'
        doc = self.folder.doc
        doc.setTitle(title)
        wrapped = IndexableObjectWrapper(doc, self.portal.portal_catalog)
        self.assertEqual(wrapped.sortable_title,
                         '2012-0006-0001 foo document')

    def testSortableLongNumberPrefix(self):
        title = '1.2.3 foo document'
        doc = self.folder.doc
        doc.setTitle(title)
        wrapped = IndexableObjectWrapper(doc, self.portal.portal_catalog)
        self.assertEqual(wrapped.sortable_title,
                         '0001.0002.0003 foo document')
        title = '1.2.3 foo program'
        doc.setTitle(title)
        wrapped = IndexableObjectWrapper(doc, self.portal.portal_catalog)
        self.assertEqual(wrapped.sortable_title,
                         '0001.0002.0003 foo program')

    def testSortableLongCommonPrefix(self):
        title = 'some documents have too long a name and only differ at ' \
            'the very end - like 1.jpeg'
        doc = self.folder.doc
        doc.setTitle(title)
        wrapped = IndexableObjectWrapper(doc, self.portal.portal_catalog)
        self.assertEqual(wrapped.sortable_title,
                         'some documents have too lon... 0001.jpeg')


class TestFolderCataloging(PloneTestCase):
    # Tests for http://dev.plone.org/plone/ticket/2876
    # folder_rename must recatalog.

    def afterSetUp(self):
        self.catalog = self.portal.portal_catalog
        self.folder.invokeFactory('Folder', id='foo')
        self.setupAuthenticator()

        # z3c.form needs IFormLayer provided by request
        alsoProvides(self.folder.REQUEST, IFormLayer)

    def testFolderTitleIsUpdatedOnFolderTitleChange(self):
        title = 'Test Folder - Snooze!'

        self.folder.REQUEST.form = {
            'form.widgets.new_id': 'foo',
            'form.widgets.new_title': title,
        }
        form = self.folder.restrictedTraverse('@@folder_rename')
        form.update()

        self.loginAsPortalOwner()
        button = form.buttons['Rename']
        form.handlers.getHandler(button)(form, button)

        results = self.catalog(Title='Snooze')
        self.assertTrue(results)
        for result in results:
            self.assertEqual(result.Title, title)
            self.assertEqual(result.getId, 'foo')

    def testFolderTitleIsUpdatedOnFolderRename(self):
        title = 'Test Folder - Snooze!'

        self.folder.REQUEST.form = {
            'form.widgets.new_id': 'bar',
            'form.widgets.new_title': title,
        }
        form = self.folder.restrictedTraverse('@@folder_rename')
        form.update()

        self.loginAsPortalOwner()
        button = form.buttons['Rename']
        form.handlers.getHandler(button)(form, button)

        results = self.catalog(Title='Snooze')
        self.assertTrue(results)
        for result in results:
            self.assertEqual(result.Title, title)
            self.assertEqual(result.getId, 'bar')

    def testSetTitleDoesNotUpdateCatalog(self):
        # setTitle() should not update the catalog
        title = 'Test Folder - Snooze!'
        self.assertTrue(self.catalog(getId='foo'))
        self.folder.foo.setTitle(title)
        #Title is a TextIndex
        self.assertFalse(self.catalog(Title='Snooze'))


class TestCatalogOrdering(PloneTestCase):

    def afterSetUp(self):
        self.catalog = self.portal.portal_catalog
        self.folder.invokeFactory('Document', id='doc1', text='foo', title='First')
        self.folder.invokeFactory('Document', id='doc2', text='bar', title='Second')
        self.folder.invokeFactory('Document', id='doc3', text='bloo', title='Third')
        self.folder.invokeFactory('Document', id='doc4', text='blee', title='Fourth')

    def testInitialOrder(self):
        self.assertEqual(self.folder.getObjectPosition('doc1'), 0)
        self.assertEqual(self.folder.getObjectPosition('doc2'), 1)
        self.assertEqual(self.folder.getObjectPosition('doc3'), 2)
        self.assertEqual(self.folder.getObjectPosition('doc4'), 3)

    def testOrderIsUnchangedOnDefaultFolderPosition(self):
        # Calling the folder_position script with no arguments should
        # give no complaints and have no effect.
        folder_position(self.folder)
        folder_docs = self.catalog(
                            portal_type='Document',
                            path='/'.join(self.folder.getPhysicalPath()),
                            sort_on='getObjPositionInParent')
        expected = ['doc1', 'doc2', 'doc3', 'doc4']
        self.assertEqual([b.getId for b in folder_docs], expected)

    def testOrderIsUpdatedOnMoveDown(self):
        folder_position(self.folder, 'down', 'doc1')
        folder_docs = self.catalog(
            portal_type='Document',
            path='/'.join(self.folder.getPhysicalPath()),
            sort_on='getObjPositionInParent',
        )
        expected = ['doc2', 'doc1', 'doc3', 'doc4']
        self.assertEqual([b.getId for b in folder_docs], expected)

    def testOrderIsUpdatedOnMoveUp(self):
        folder_position(self.folder, 'up', 'doc3')
        folder_docs = self.catalog(
            portal_type='Document',
            path='/'.join(self.folder.getPhysicalPath()),
            sort_on='getObjPositionInParent',
        )
        expected = ['doc1', 'doc3', 'doc2', 'doc4']
        self.assertEqual([b.getId for b in folder_docs], expected)

    def testOrderIsUpdatedOnMoveTop(self):
        folder_position(self.folder, 'top', 'doc3')
        folder_docs = self.catalog(
            portal_type='Document',
            path='/'.join(self.folder.getPhysicalPath()),
            sort_on='getObjPositionInParent'
        )
        expected = ['doc3', 'doc1', 'doc2', 'doc4']
        self.assertEqual([b.getId for b in folder_docs], expected)

    def testOrderIsUpdatedOnMoveBottom(self):
        folder_position(self.folder, 'bottom', 'doc3')
        folder_docs = self.catalog(
            portal_type='Document',
            path='/'.join(self.folder.getPhysicalPath()),
            sort_on='getObjPositionInParent'
        )
        expected = ['doc1', 'doc2', 'doc4', 'doc3']
        self.assertEqual([b.getId for b in folder_docs], expected)

    def testOrderIsUpdatedOnSort(self):
        folder_position(self.folder, position='ordered', id='Title')
        folder_docs = self.catalog(
                            portal_type='Document',
                            path='/'.join(self.folder.getPhysicalPath()),
                            sort_on='getObjPositionInParent')
        expected = ['doc1', 'doc4', 'doc2', 'doc3']
        self.assertEqual([b.getId for b in folder_docs], expected)

    def testOrderIsUpdatedOnReverse(self):
        folder_position(self.folder, position='ordered', id='Title', reverse=True)
        folder_docs = self.catalog(
                            portal_type='Document',
                            path='/'.join(self.folder.getPhysicalPath()),
                            sort_on='getObjPositionInParent')
        expected = ['doc3', 'doc2', 'doc4', 'doc1']
        self.assertEqual([b.getId for b in folder_docs], expected)

    def testOrderIsUpdatedOnSimpleReverse(self):
        folder_position(self.folder, position='ordered', reverse=True)
        folder_docs = self.catalog(
                            portal_type='Document',
                            path='/'.join(self.folder.getPhysicalPath()),
                            sort_on='getObjPositionInParent')
        expected = ['doc4', 'doc3', 'doc2', 'doc1']
        self.assertEqual([b.getId for b in folder_docs], expected)

    def testOrderIsFineWithObjectCreation(self):
        self.folder.invokeFactory('Document', id='doc5', text='blam')
        folder_docs = self.catalog(
            portal_type='Document',
            path='/'.join(self.folder.getPhysicalPath()),
            sort_on='getObjPositionInParent'
        )
        expected = ['doc1', 'doc2', 'doc3', 'doc4', 'doc5']
        self.assertEqual([b.getId for b in folder_docs], expected)

    def testOrderIsFineWithObjectDeletion(self):
        self.folder.manage_delObjects(['doc3', ])
        folder_docs = self.catalog(
            portal_type='Document',
            path='/'.join(self.folder.getPhysicalPath()),
            sort_on='getObjPositionInParent'
        )
        expected = ['doc1', 'doc2', 'doc4']
        self.assertEqual([b.getId for b in folder_docs], expected)

    def testOrderIsFineWithObjectRenaming(self):

        # I don't know why this is failing. manage_renameObjects throws an
        # error that blames permissions or lack of support by the obj. The
        # obj is a # Plone Document, and the owner of doc2 is portal_owner.
        # Harumph.

        transaction.savepoint(optimistic=True)

        self.folder.manage_renameObjects(['doc2'], ['buzz'])
        folder_docs = self.catalog(
            portal_type='Document',
            path='/'.join(self.folder.getPhysicalPath()),
            sort_on='getObjPositionInParent'
        )
        expected = ['doc1', 'buzz', 'doc3', 'doc4']
        self.assertEqual([b.getId for b in folder_docs], expected)

    def testOrderAfterALotOfChanges(self):
        # ['doc1','doc2','doc3','doc4']

        folder_position(self.folder, 'down', 'doc1')
        folder_position(self.folder, 'down', 'doc1')
        # ['doc2','doc3','doc1','doc4']

        folder_position(self.folder, 'top', 'doc3')
        # ['doc3','doc2','doc1','doc4']

        self.folder.invokeFactory('Document', id='doc5', text='blam')
        self.folder.invokeFactory('Document', id='doc6', text='blam')
        self.folder.invokeFactory('Document', id='doc7', text='blam')
        self.folder.invokeFactory('Document', id='doc8', text='blam')
        # ['doc3','doc2','doc1','doc4','doc5','doc6','doc7','doc8',]

        #self.folder.manage_renameObjects('Document', id='doc5', text='blam')

        self.folder.manage_delObjects(['doc3', 'doc4', 'doc5', 'doc7'])
        expected = ['doc2', 'doc1', 'doc6', 'doc8']

        folder_docs = self.catalog(
            portal_type='Document',
            path='/'.join(self.folder.getPhysicalPath()),
            sort_on='getObjPositionInParent',
        )
        self.assertEqual([b.getId for b in folder_docs], expected)

    def testAllObjectsHaveOrder(self):
        #Make sure that a query with sort_on='getObjPositionInParent'
        #returns the same number of results as one without, make sure
        #the Members folder is in the catalog and has getObjPositionInParent
        all_objs = self.catalog()
        sorted_objs = self.catalog(sort_on='getObjPositionInParent')
        self.assertEqual(len(all_objs), len(sorted_objs))

        members = self.portal.Members
        members_path = '/'.join(members.getPhysicalPath())
        members_query = self.catalog(path=members_path)
        members_sorted = self.catalog(
            path=members_path,
            sort_on='getObjPositionInParent',
        )
        self.assertTrue(len(members_query))
        self.assertEqual(len(members_query), len(members_sorted))

    def testGopipIndexer(self):
        from Products.CMFPlone.CatalogTool import getObjPositionInParent
        get_pos = getObjPositionInParent.callable
        self.assertEqual(get_pos(self.folder.doc1), 0)
        self.assertEqual(get_pos(self.folder.doc4), 3)


class TestCatalogBugs(PloneTestCase):

    def afterSetUp(self):
        self.catalog = self.portal.portal_catalog
        # Make the catalog tool paste-able
        self._saved = CatalogTool.__replaceable__
        CatalogTool.__replaceable__ = REPLACEABLE

    def afterClear(self):
        CatalogTool.__replaceable__ = self._saved

    def testCanPasteCatalog(self):
        # Should be able to copy/paste a portal_catalog. Triggers
        # manage_afterAdd of portal_catalog thereby exposing another bug :-/
        self.setRoles(['Manager'])
        cb = self.portal.manage_copyObjects(['portal_catalog'])
        self.folder.manage_pasteObjects(cb)
        self.assertTrue(hasattr(aq_base(self.folder), 'portal_catalog'))

    def testPastingCatalogPreservesTextIndexes(self):
        # Pasting the catalog should not cause indexes to be removed.
        self.setRoles(['Manager'])
        cb = self.portal.manage_copyObjects(['portal_catalog'])
        self.folder.manage_pasteObjects(cb)
        self.assertTrue(hasattr(aq_base(self.folder), 'portal_catalog'))
        cat = self.folder.portal_catalog
        self.assertTrue('SearchableText' in cat.indexes())
        # CMF added lexicons should stick around too
        self.assertTrue(hasattr(aq_base(cat), 'plaintext_lexicon'))


class TestCatalogUnindexing(PloneTestCase):
    # Tests for http://dev.plone.org/plone/ticket/3547
    # Published objects are not unindexed on delete?

    def afterSetUp(self):
        self.catalog = self.portal.portal_catalog
        self.workflow = self.portal.portal_workflow
        self.folder.invokeFactory('Document', id='doc')
        self.setupAuthenticator()

    def testVisibleIsDefault(self):
        state = self.workflow.getInfoFor(self.folder.doc, 'review_state')
        self.assertEqual(state, 'visible')

    def testVisibleCanBeFound(self):
        self.assertTrue(self.catalog(getId='doc'))

    def testVisibleIsUnindexed(self):
        self.folder._delObject('doc')
        self.assertFalse(self.catalog(getId='doc'))

    def testPrivateCanBeFound(self):
        self.workflow.doActionFor(self.folder.doc, 'hide')
        self.assertTrue(self.catalog(getId='doc'))

    def testPrivateIsUnindexed(self):
        self.workflow.doActionFor(self.folder.doc, 'hide')
        self.folder._delObject('doc')
        self.assertFalse(self.catalog(getId='doc'))

    def testPendingCanBeFound(self):
        self.workflow.doActionFor(self.folder.doc, 'submit')
        self.assertTrue(self.catalog(getId='doc'))

    def testPendingIsUnindexed(self):
        self.workflow.doActionFor(self.folder.doc, 'submit')
        self.folder._delObject('doc')
        self.assertFalse(self.catalog(getId='doc'))

    def testPublishedCanBeFound(self):
        self.setRoles(['Manager'])
        self.workflow.doActionFor(self.folder.doc, 'publish')
        self.assertTrue(self.catalog(getId='doc'))

    def testPublishedIsUnindexed(self):
        self.setRoles(['Manager'])
        self.workflow.doActionFor(self.folder.doc, 'publish')
        self.folder._delObject('doc')
        self.assertFalse(self.catalog(getId='doc'))

    def testPublishedIsUnindexedIfOwnerDeletes(self):
        self.setRoles(['Manager'])
        self.workflow.doActionFor(self.folder.doc, 'publish')
        self.setRoles(['Member'])
        self.folder._delObject('doc')
        self.assertFalse(self.catalog(getId='doc'))

    def testPublishedIsUnindexedByFolderDeleteScript(self):
        doc = self.folder.doc
        self.setRoles(['Manager'])
        self.workflow.doActionFor(doc, 'publish')
        self.setRoles(['Member'])
        alsoProvides(doc.REQUEST, IFormLayer)
        doc.restrictedTraverse('@@object_delete')()
        self.assertFalse(self.catalog(getId='doc'))

    def testPublishedIsUnindexedWhenDeletingParentFolder(self):
        # Works here too!
        self.setRoles(['Manager'])
        self.workflow.doActionFor(self.folder.doc, 'publish')
        self.setRoles(['Member'])
        self.folder.aq_parent._delObject(self.folder.getId())
        self.assertFalse(self.catalog(getId='doc'))


class TestCatalogExpirationFiltering(PloneTestCase):

    def afterSetUp(self):
        self.catalog = self.portal.portal_catalog
        self.folder.invokeFactory('Document', id='doc')

    def nofx(self):
        # Removes effective and expires to make sure we only test
        # the DateRangeIndex.
        self.catalog.delIndex('effective')
        self.catalog.delIndex('expires')

    def assertResults(self, result, expect):
        # Verifies ids of catalog results against expected ids
        lhs = [r.getId for r in result]
        lhs.sort()
        rhs = list(expect)
        rhs.sort()
        self.assertEqual(lhs, rhs)

    def testCeilingPatch(self):
        self.assertEqual(self.folder.doc.expires(), DateTime(2500, 0))

    def testSearchResults(self):
        res = self.catalog.searchResults()
        self.assertResults(res, base_content)

    def testCall(self):
        res = self.catalog()
        self.assertResults(res, base_content)

    def testSearchResultsExpired(self):
        self.folder.doc.setExpirationDate(DateTime(2000, 12, 31))
        self.folder.doc.reindexObject()
        self.nofx()
        res = self.catalog.searchResults()
        self.assertResults(res, base_content[:-1])

    def testCallExpired(self):
        self.folder.doc.setExpirationDate(DateTime(2000, 12, 31))
        self.folder.doc.reindexObject()
        self.nofx()
        res = self.catalog()
        self.assertResults(res, base_content[:-1])

    def testSearchResultsExpiredWithExpiredDisabled(self):
        self.folder.doc.setExpirationDate(DateTime(2000, 12, 31))
        self.folder.doc.reindexObject()
        self.nofx()
        res = self.catalog.searchResults(dict(show_inactive=True))
        self.assertResults(res, base_content)

    def testCallExpiredWithExpiredDisabled(self):
        self.folder.doc.setExpirationDate(DateTime(2000, 12, 31))
        self.folder.doc.reindexObject()
        self.nofx()
        res = self.catalog(show_inactive=True)
        self.assertResults(res, base_content)

    def testSearchResultsExpiredWithPermission(self):
        self.folder.doc.setExpirationDate(DateTime(2000, 12, 31))
        self.folder.doc.reindexObject()
        self.nofx()
        self.setPermissions([AccessInactivePortalContent])
        res = self.catalog.searchResults()
        self.assertResults(res, base_content)

    def testCallExpiredWithPermission(self):
        self.folder.doc.setExpirationDate(DateTime(2000, 12, 31))
        self.folder.doc.reindexObject()
        self.nofx()
        self.setPermissions([AccessInactivePortalContent])
        res = self.catalog()
        self.assertResults(res, base_content)

    def testSearchResultsWithAdditionalExpiryFilter(self):
        # For this test we want the expires and effective indices in place,
        # let's make sure everything still works
        self.folder.doc.setExpirationDate(DateTime(2000, 12, 31))
        self.folder.doc.reindexObject()
        res = self.catalog.searchResults()
        self.assertResults(res, base_content[:-1])
        # Now make the object expire at some fixed date in the future
        self.folder.doc.setExpirationDate(DateTime() + 2)
        self.folder.doc.reindexObject()
        res = self.catalog.searchResults()
        self.assertResults(res, base_content)
        # We should be able to further limit the search using the exipres
        # and efective indices.
        res = self.catalog.searchResults({
            'expires': {'query': DateTime() + 3, 'range': 'min'},
        })

        self.assertResults(res, base_content[:-1])

    def testSearchResultsExpiredWithAdditionalExpiryFilter(self):
        # Now make the object expire at some date in the recent past
        self.folder.doc.setExpirationDate(DateTime() - 2)
        self.folder.doc.reindexObject()
        res = self.catalog.searchResults()
        self.assertResults(res, base_content[:-1])
        # Even if we explicitly ask for it, we shouldn't get expired content
        res = self.catalog.searchResults({
            'expires': {'query': DateTime() - 3, 'range': 'min'},
        })
        self.assertResults(res, base_content[:-1])


def dummyMethod(obj, **kwargs):
    return 'a dummy'


class TestIndexers(PloneTestCase):
    """Tests for IIndexer adapters
    """

    def afterSetUp(self):
        self.folder.invokeFactory('Document', 'doc', title='document')
        self.doc = self.folder.doc

    def testSetup(self):
        doc = self.doc
        self.assertEqual(doc.getId(), 'doc')
        self.assertEqual(doc.Title(), 'document')

    def test_is_folderishWithNonFolder(self):
        i = dummy.Item()
        self.assertFalse(is_folderish(i)())

    def test_is_folderishWithFolder(self):
        f = dummy.Folder('struct_folder')
        self.assertTrue(is_folderish(f)())

    def test_is_folderishWithNonStructuralFolder(self):
        f = dummy.NonStructuralFolder('ns_folder')
        self.assertFalse(is_folderish(f)())

    def test_provided(self):
        from Products.CMFCore.interfaces import IContentish
        from plone.indexer.interfaces import IIndexableObjectWrapper
        from Products.CMFCore.tests.base.dummy import DummyContent

        obj = DummyContent()
        w = IndexableObjectWrapper(obj, self.portal.portal_catalog)

        self.assertTrue(IIndexableObjectWrapper.providedBy(w))
        self.assertTrue(IContentish.providedBy(w))

    def test_getIcon(self):
        doc = self.doc
        iconname = doc.getIcon(relative_to_portal=1)
        wrapped = IndexableObjectWrapper(doc, self.portal.portal_catalog)
        self.assertEqual(wrapped.getIcon, iconname)

    def test_getObjSize(self):
        from Products.CMFPlone.CatalogTool import getObjSize
        get_size = getObjSize.callable
        self.doc.setText(u'a' * 1000)
        self.doc.reindexObject()
        self.assertEqual(get_size(self.doc), '1 KB')

    def test_uuid(self):
        alsoProvides(self.doc, IAttributeUUID)
        notify(ObjectCreatedEvent(self.doc))

        uuid = IUUID(self.doc, None)
        wrapped = IndexableObjectWrapper(self.doc, self.portal.portal_catalog)
        self.assertTrue(wrapped.UID)
        self.assertTrue(uuid == wrapped.UID)


class TestMetadata(PloneTestCase):

    def testLocationAddedToMetdata(self):
        self.folder.invokeFactory(
            'Document', 'doc', title='document', location="foobar")
        doc = self.folder.doc
        catalog = self.portal.portal_catalog
        brain = catalog(UID=doc.UID())[0]
        self.assertEqual(brain.location, doc.getLocation())


class TestObjectProvidedIndexExtender(unittest.TestCase):

    def _index(self, object):
        from Products.CMFPlone.CatalogTool import object_provides
        return object_provides(object)()

    def testNoInterfaces(self):
        class Dummy(object):
            pass
        self.assertEqual(self._index(Dummy()), ())

    def testSimpleInterface(self):
        class IDummy(zope.interface.Interface):
            pass

        class Dummy(object):
            zope.interface.implements(IDummy)
        self.assertEqual(
            self._index(Dummy()),
            ('Products.CMFPlone.tests.testCatalogTool.IDummy', )
        )
