vendor/CMF/1.5.3/CMFSetup

changeset 0:3ed006215eb6 CMFSetup tip

Vendor import of CMF 1.5.3
author fguillaume
date Tue, 09 Aug 2005 10:47:34 +0000
parents
children
files CREDITS.txt DEPENDENCIES.txt PROFILES.txt README.txt __init__.py actions.py context.py differ.py exceptions.py factory.py interfaces.py permissions.py properties.py registry.py rolemap.py skins.py tests/__init__.py tests/common.py tests/conformance.py tests/default_profile/export_steps.xml tests/default_profile/import_steps.xml tests/default_profile/profile.ini tests/default_profile/toolset.xml tests/four/placeholder.txt tests/one/placeholder.txt tests/simple.png tests/test_actions.py tests/test_all.py tests/test_context.py tests/test_differ.py tests/test_properties.py tests/test_registry.py tests/test_rolemap.py tests/test_skins.py tests/test_tool.py tests/test_typeinfo.py tests/test_utils.py tests/test_workflow.py tests/three/placeholder.txt tests/two/placeholder.txt tool.py typeinfo.py utils.py version.txt workflow.py www/siteAddForm.zpt www/sutCompare.zpt www/sutExportSteps.zpt www/sutImportSteps.zpt www/sutProperties.zpt www/sutSnapshots.zpt www/tool.png xml/apcExport.xml xml/esrExport.xml xml/isrExport.xml xml/object_nodes.xml xml/property_nodes.xml xml/rmeExport.xml xml/spcExport.xml xml/stcExport.xml xml/ticToolExport.xml xml/ticTypeExport.xml xml/tscExport.xml xml/wtcToolExport.xml xml/wtcWorkflowExport.xml
diffstat 65 files changed, 16900 insertions(+), 0 deletions(-) [+]
line diff
     1.1 new file mode 100644
     1.2 --- /dev/null
     1.3 +++ b/CREDITS.txt
     1.4 @@ -0,0 +1,7 @@
     1.5 +CMFSetup Credits
     1.6 +
     1.7 +  - Martijn Pieters <mj@zopatista.com> wrote the original version of this
     1.8 +    software while working at Zope Corporation.
     1.9 +
    1.10 +  - He developed his version as part of the "Bonzai" CMS which Zope corp.
    1.11 +    built Boston.com using its Zope4Media Print product.
     2.1 new file mode 100644
     2.2 --- /dev/null
     2.3 +++ b/DEPENDENCIES.txt
     2.4 @@ -0,0 +1,4 @@
     2.5 +Zope >= 2.7.0
     2.6 +CMFCore
     2.7 +CMFDefault
     2.8 +DCWorkflow
     3.1 new file mode 100644
     3.2 --- /dev/null
     3.3 +++ b/PROFILES.txt
     3.4 @@ -0,0 +1,40 @@
     3.5 +Profiles
     3.6 +
     3.7 +  Overview
     3.8 +
     3.9 +    There are two different kinds of profiles: Base profiles and extension
    3.10 +    profiles. Base profiles have no dependencies. Extension profiles are
    3.11 +    profile fragments used to modify base profiles. They can be shipped with
    3.12 +    add-on products or used for customization steps. Importing an extension
    3.13 +    profile adds or overwrites existing settings in a fine-grained way. You
    3.14 +    can't export extension profiles. Snapshots and exports always represent
    3.15 +    the merged settings.
    3.16 +
    3.17 +  Update Directives
    3.18 +
    3.19 +    For some XML elements there are additional attributes and values to
    3.20 +    specify update directives. They are only useful for extension profiles and
    3.21 +    you will never see them in snapshots and exports.
    3.22 +
    3.23 +    'insert-before' and 'insert-after'
    3.24 +
    3.25 +      applies to: object (generic); layer (skins.xml)
    3.26 +
    3.27 +      'insert-before' and 'insert-after' specify the position of a new item
    3.28 +      relative to an existing item. If they are omitted or not valid, items
    3.29 +      are appended. You can also use '*' as wildcard. This will insert the new
    3.30 +      item at the top (before all existing items) or the bottom (after all
    3.31 +      existing items). If an item with the given ID exists already, it is
    3.32 +      moved to the specified position.
    3.33 +
    3.34 +    'id="*"' wildcard
    3.35 +
    3.36 +      applies to: skin-path (skins.xml)
    3.37 +
    3.38 +      Updates all existing items in the container with the same settings.
    3.39 +
    3.40 +    'remove'
    3.41 +
    3.42 +      applies to: action-provider, skin layer (actions.xml, skins.xml)
    3.43 +
    3.44 +      Removes the specified item if it exists.
     4.1 new file mode 100644
     4.2 --- /dev/null
     4.3 +++ b/README.txt
     4.4 @@ -0,0 +1,126 @@
     4.5 +CMFSetup Product README
     4.6 +
     4.7 +  Overview
     4.8 +
     4.9 +    This product provides a mini-framework for expressing the configured
    4.10 +    state of a CMF Site as a set of filesystem artifacts.  These artifacts
    4.11 +    consist of declarative XML files, which spell out the configuration
    4.12 +    settings for each tool, and supporting scripts / templates, in their
    4.13 +    "canonical" filesystem representations.
    4.14 +
    4.15 +  Configurations Included
    4.16 +
    4.17 +    The 'portal_setup' tool knows how to export / import configurations
    4.18 +    and scripts for the following tools:
    4.19 +
    4.20 +      - (x) removal / creation of specified tools
    4.21 +
    4.22 +      - (x) itself :)
    4.23 +
    4.24 +      - (x) the role / permission map on the site object
    4.25 +
    4.26 +      - (x) properties of the site object
    4.27 +
    4.28 +      - (x) 'portal_actions'
    4.29 +            (Products.CMFCore.ActionsTool.ActionsTool)
    4.30 +
    4.31 +            o action providers, and their actions; note that this removes
    4.32 +              the requirement to have individual tools configure their own
    4.33 +              actions
    4.34 +
    4.35 +      - (x) 'portal_skins'
    4.36 +
    4.37 +            o tool properties
    4.38 +
    4.39 +            o FilesystemDirectoryView instances
    4.40 +
    4.41 +            o skin path definitions
    4.42 +
    4.43 +      - (x) 'portal_types'
    4.44 +
    4.45 +            o content type definitions, including actions
    4.46 +
    4.47 +      - (x) 'portal_workflow'
    4.48 +
    4.49 +            o bindings of workflows to content types
    4.50 +
    4.51 +            o DCWorkflow definitions, including supporting scripts
    4.52 +
    4.53 +      - (_) 'portal_catalog'
    4.54 +            (Products.CMFCore.CatalogTool.CatalogTool)
    4.55 +
    4.56 +            o index names / types
    4.57 +
    4.58 +            o metadata column names
    4.59 +
    4.60 +      - (_) 'portal_membership'
    4.61 +
    4.62 +            o "skeleton" home folder (XXX: is this in the core?)
    4.63 +
    4.64 +      - (_) 'portal_memberdata'
    4.65 +
    4.66 +            o member properties
    4.67 +
    4.68 +      - (_) 'content_type_registry'
    4.69 +
    4.70 +            o predicate -> portal_type bindings.
    4.71 +
    4.72 +      - (_) 'caching_policy_manager'
    4.73 +
    4.74 +            o policy settings
    4.75 +
    4.76 +      - (_) 'portal_metadata'
    4.77 +
    4.78 +            o global properties
    4.79 +
    4.80 +            o default element policies
    4.81 +
    4.82 +            o type-specific element policies
    4.83 +
    4.84 +      - (_) 'portal_actionicons'
    4.85 +            (Products.CMFActionIcons.ActionIconsTool.ActionIconsTool)
    4.86 +
    4.87 +            o action title / icon bindings
    4.88 +
    4.89 +      - (_) 'cookie_authentication'
    4.90 +
    4.91 +            o tool properties
    4.92 +
    4.93 +      - (_) 'MailHost'
    4.94 +
    4.95 +            o tool properties
    4.96 +
    4.97 +      - (_) user folder configuration
    4.98 +
    4.99 +      - (_) folder structure
   4.100 +
   4.101 +  TODO
   4.102 +
   4.103 +      - (x) Display / download diffs between configurations (profiles
   4.104 +            and shapshots) Done 2004/07/20, TS.
   4.105 +
   4.106 +      - (x) Modify profile selection to use a drop-down list of registered
   4.107 +            profiles.
   4.108 +
   4.109 +      - (x) Allow import from snapshots.
   4.110 +
   4.111 +  Extending The Tool
   4.112 +
   4.113 +    Third-party products extend the tool by registering handlers for
   4.114 +    import / export of their unique tools.
   4.115 +
   4.116 +  Glossary
   4.117 +
   4.118 +    Site --
   4.119 +      The instance in the Zope URL space which defines a "zone of service"
   4.120 +      for a set of CMF tools.
   4.121 +
   4.122 +    Profile --
   4.123 +      A "preset" configuration of a site, defined on the filesystem
   4.124 +
   4.125 +    Snapshot --
   4.126 +      "Frozen" site configuration, captured within the setup tool
   4.127 +
   4.128 +    "dotted name" --
   4.129 +      The Pythonic representation of the "path" to a given function /
   4.130 +      module, e.g. 'Products.CMFCore.utils.getToolByName'.
     5.1 new file mode 100644
     5.2 --- /dev/null
     5.3 +++ b/__init__.py
     5.4 @@ -0,0 +1,52 @@
     5.5 +##############################################################################
     5.6 +#
     5.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
     5.8 +#
     5.9 +# This software is subject to the provisions of the Zope Public License,
    5.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
    5.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
    5.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    5.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
    5.14 +# FOR A PARTICULAR PURPOSE.
    5.15 +#
    5.16 +##############################################################################
    5.17 +""" CMFSetup product initialization.
    5.18 +
    5.19 +$Id: __init__.py 37118 2005-07-07 16:52:34Z regebro $
    5.20 +"""
    5.21 +
    5.22 +from AccessControl import ModuleSecurityInfo
    5.23 +
    5.24 +from interfaces import BASE, EXTENSION
    5.25 +from permissions import ManagePortal
    5.26 +from registry import _profile_registry as profile_registry
    5.27 +
    5.28 +security = ModuleSecurityInfo( 'Products.CMFSetup' )
    5.29 +security.declareProtected( ManagePortal, 'profile_registry' )
    5.30 +
    5.31 +def initialize( context ):
    5.32 +
    5.33 +    from Products.CMFCore.utils import ToolInit, registerIcon
    5.34 +    from tool import SetupTool
    5.35 +
    5.36 +
    5.37 +    ToolInit( 'CMF Setup Tool'
    5.38 +            , tools=[ SetupTool ]
    5.39 +            , icon=None
    5.40 +            ).initialize( context )
    5.41 +
    5.42 +    registerIcon(  SetupTool, 'www/tool.png', globals() )
    5.43 +
    5.44 +    from factory import addConfiguredSiteForm
    5.45 +    from factory import addConfiguredSite
    5.46 +
    5.47 +    # Add factory for a site which follows a profile.  We specify
    5.48 +    # meta_type and interfaces because we don't actually register a
    5.49 +    # class here, only a factory.
    5.50 +    context.registerClass( meta_type='Configured CMF Site'
    5.51 +                         , constructors=( addConfiguredSiteForm
    5.52 +                                        , addConfiguredSite
    5.53 +                                        )
    5.54 +                         , permissions=( 'Add CMF Sites', )
    5.55 +                         , interfaces=None
    5.56 +                         )
     6.1 new file mode 100644
     6.2 --- /dev/null
     6.3 +++ b/actions.py
     6.4 @@ -0,0 +1,191 @@
     6.5 +##############################################################################
     6.6 +#
     6.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
     6.8 +#
     6.9 +# This software is subject to the provisions of the Zope Public License,
    6.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
    6.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
    6.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    6.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
    6.14 +# FOR A PARTICULAR PURPOSE.
    6.15 +#
    6.16 +##############################################################################
    6.17 +""" Classes:  ActionsProviderConfigurator
    6.18 +
    6.19 +$Id: actions.py 36924 2005-04-13 13:05:02Z yuppie $
    6.20 +"""
    6.21 +
    6.22 +from AccessControl import ClassSecurityInfo
    6.23 +from Globals import InitializeClass
    6.24 +from Products.PageTemplates.PageTemplateFile import PageTemplateFile
    6.25 +
    6.26 +from Products.CMFCore.ActionInformation import ActionInformation
    6.27 +from Products.CMFCore.ActionProviderBase import IActionProvider
    6.28 +from Products.CMFCore.utils import getToolByName
    6.29 +
    6.30 +from permissions import ManagePortal
    6.31 +from utils import _xmldir
    6.32 +from utils import ConfiguratorBase
    6.33 +from utils import CONVERTER, DEFAULT, KEY
    6.34 +
    6.35 +
    6.36 +#
    6.37 +#   Configurator entry points
    6.38 +#
    6.39 +_FILENAME = 'actions.xml'
    6.40 +
    6.41 +def importActionProviders( context ):
    6.42 +
    6.43 +    """ Import action providers and their actions from an XML file.
    6.44 +
    6.45 +    o 'context' must implement IImportContext.
    6.46 +
    6.47 +    o Register via Python:
    6.48 +
    6.49 +      registry = site.portal_setup.getImportStepRegistry()
    6.50 +      registry.registerStep( 'importActionProviders'
    6.51 +                           , '20040518-01'
    6.52 +                           , Products.CMFSetup.actions.importActionProviders
    6.53 +                           , ()
    6.54 +                           , 'Action Provider import'
    6.55 +                           , 'Import  action providers registered with '
    6.56 +                             'the actions tool, and their actions.'
    6.57 +                           )
    6.58 +
    6.59 +    o Register via XML:
    6.60 +
    6.61 +      <setup-step id="importActionProviders"
    6.62 +                  version="20040524-01"
    6.63 +                  handler="Products.CMFSetup.actions.importActionProviders"
    6.64 +                  title="Action Provider import"
    6.65 +      >Import action providers registered with the actions tool,
    6.66 +       and their actions.</setup-step>
    6.67 +
    6.68 +    """
    6.69 +    site = context.getSite()
    6.70 +    encoding = context.getEncoding()
    6.71 +
    6.72 +    actions_tool = getToolByName( site, 'portal_actions' )
    6.73 +
    6.74 +    if context.shouldPurge():
    6.75 +
    6.76 +        for provider_id in actions_tool.listActionProviders():
    6.77 +            actions_tool.deleteActionProvider( provider_id )
    6.78 +
    6.79 +    xml = context.readDataFile(_FILENAME)
    6.80 +    if xml is None:
    6.81 +        return 'Action providers: Nothing to import.'
    6.82 +
    6.83 +    apc = ActionProvidersConfigurator(site, encoding)
    6.84 +    tool_info = apc.parseXML(xml)
    6.85 +
    6.86 +    for p_info in tool_info['providers']:
    6.87 +
    6.88 +        if 'remove' in p_info:
    6.89 +            if p_info['id'] in actions_tool.listActionProviders():
    6.90 +                actions_tool.deleteActionProvider(p_info['id'])
    6.91 +            continue
    6.92 +
    6.93 +        if p_info['id'] not in actions_tool.listActionProviders():
    6.94 +
    6.95 +            actions_tool.addActionProvider(p_info['id'])
    6.96 +
    6.97 +        provider = getToolByName(site, p_info['id'])
    6.98 +        provider._actions = [ ActionInformation(**a_info)
    6.99 +                              for a_info in p_info[ 'actions' ] ]
   6.100 +
   6.101 +    return 'Action providers imported.'
   6.102 +
   6.103 +def exportActionProviders( context ):
   6.104 +
   6.105 +    """ Export action providers and their actions as an XML file
   6.106 +
   6.107 +    o 'context' must implement IExportContext.
   6.108 +
   6.109 +    o Register via Python:
   6.110 +
   6.111 +      registry = site.portal_setup.getExportStepRegistry()
   6.112 +      registry.registerStep( 'exportActionProviders'
   6.113 +                           , Products.CMFSetup.actions.exportActionProviders
   6.114 +                           , 'Action Provider export'
   6.115 +                           , 'Export action providers registered with '
   6.116 +                             'the actions tool, and their actions.'
   6.117 +                           )
   6.118 +
   6.119 +    o Register via XML:
   6.120 +
   6.121 +      <export-script id="exportActionProviders"
   6.122 +                     version="20040518-01"
   6.123 +                     handler="Products.CMFSetup.actions.exportActionProviders"
   6.124 +                     title="Action Provider export"
   6.125 +      >Export action providers registered with the actions tool,
   6.126 +       and their actions.</export-script>
   6.127 +
   6.128 +    """
   6.129 +    site = context.getSite()
   6.130 +    apc = ActionProvidersConfigurator( site ).__of__( site )
   6.131 +    text = apc.generateXML()
   6.132 +
   6.133 +    context.writeDataFile( _FILENAME, text, 'text/xml' )
   6.134 +
   6.135 +    return 'Action providers exported.'
   6.136 +
   6.137 +
   6.138 +class ActionProvidersConfigurator(ConfiguratorBase):
   6.139 +    """ Synthesize XML description of site's action providers.
   6.140 +    """
   6.141 +    security = ClassSecurityInfo()
   6.142 +
   6.143 +    security.declareProtected( ManagePortal, 'listProviderInfo' )
   6.144 +    def listProviderInfo( self ):
   6.145 +
   6.146 +        """ Return a sequence of mappings for each action provider.
   6.147 +        """
   6.148 +        actions_tool = getToolByName( self._site, 'portal_actions' )
   6.149 +        result = []
   6.150 +
   6.151 +        for provider_id in actions_tool.listActionProviders():
   6.152 +
   6.153 +            provider_info = { 'id' : provider_id, 'actions' : [] }
   6.154 +            result.append( provider_info )
   6.155 +
   6.156 +            provider = getToolByName( self._site, provider_id )
   6.157 +
   6.158 +            if not IActionProvider.isImplementedBy( provider ):
   6.159 +                continue
   6.160 +
   6.161 +            actions = provider.listActions()
   6.162 +
   6.163 +            if actions and isinstance(actions[0], dict):
   6.164 +                continue
   6.165 +
   6.166 +            provider_info['actions'] = [ ai.getMapping() for ai in actions ]
   6.167 +
   6.168 +        return result
   6.169 +
   6.170 +    def _getExportTemplate(self):
   6.171 +
   6.172 +        return PageTemplateFile('apcExport.xml', _xmldir)
   6.173 +
   6.174 +    def _getImportMapping(self):
   6.175 +
   6.176 +        return {
   6.177 +          'actions-tool':
   6.178 +             { 'action-provider': {KEY: 'providers', DEFAULT: ()} },
   6.179 +          'action-provider':
   6.180 +             { 'id':              {},
   6.181 +               'remove':          {},
   6.182 +               'action':          {KEY: 'actions', DEFAULT: ()} },
   6.183 +          'action':
   6.184 +             { 'action_id':       {KEY: 'id'},
   6.185 +               'title':           {},
   6.186 +               'description':     {CONVERTER: self._convertToUnique},
   6.187 +               'category':        {},
   6.188 +               'condition_expr':  {KEY: 'condition'},
   6.189 +               'permission':      {KEY: 'permissions', DEFAULT: ()},
   6.190 +               'visible':         {CONVERTER: self._convertToBoolean},
   6.191 +               'url_expr':        {KEY: 'action'} },
   6.192 +          'permission':
   6.193 +             { '#text':           {KEY: None} } }
   6.194 +
   6.195 +InitializeClass(ActionProvidersConfigurator)
     7.1 new file mode 100644
     7.2 --- /dev/null
     7.3 +++ b/context.py
     7.4 @@ -0,0 +1,462 @@
     7.5 +##############################################################################
     7.6 +#
     7.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
     7.8 +#
     7.9 +# This software is subject to the provisions of the Zope Public License,
    7.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
    7.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
    7.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    7.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
    7.14 +# FOR A PARTICULAR PURPOSE.
    7.15 +#
    7.16 +##############################################################################
    7.17 +""" Various context implementations for export / import of configurations.
    7.18 +
    7.19 +Wrappers representing the state of an import / export operation.
    7.20 +
    7.21 +$Id: context.py 37351 2005-07-20 21:16:59Z jens $
    7.22 +"""
    7.23 +
    7.24 +import os
    7.25 +import time
    7.26 +from StringIO import StringIO
    7.27 +from tarfile import TarFile
    7.28 +from tarfile import TarInfo
    7.29 +
    7.30 +from AccessControl import ClassSecurityInfo
    7.31 +from Acquisition import aq_inner
    7.32 +from Acquisition import aq_parent
    7.33 +from Acquisition import aq_self
    7.34 +from Acquisition import Implicit
    7.35 +from DateTime.DateTime import DateTime
    7.36 +from Globals import InitializeClass
    7.37 +from OFS.DTMLDocument import DTMLDocument
    7.38 +from OFS.Folder import Folder
    7.39 +from OFS.Image import File
    7.40 +from OFS.Image import Image
    7.41 +from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
    7.42 +from Products.PythonScripts.PythonScript import PythonScript
    7.43 +
    7.44 +from interfaces import IExportContext
    7.45 +from interfaces import IImportContext
    7.46 +from permissions import ManagePortal
    7.47 +
    7.48 +
    7.49 +class DirectoryImportContext( Implicit ):
    7.50 +
    7.51 +    __implements__ = ( IImportContext, )
    7.52 +
    7.53 +    security = ClassSecurityInfo()
    7.54 +
    7.55 +    def __init__( self
    7.56 +                , tool
    7.57 +                , profile_path
    7.58 +                , should_purge=False
    7.59 +                , encoding=None
    7.60 +                ):
    7.61 +
    7.62 +        self._site = aq_parent( aq_inner( tool ) )
    7.63 +        self._profile_path = profile_path
    7.64 +        self._should_purge = bool( should_purge )
    7.65 +        self._encoding = encoding
    7.66 +
    7.67 +    security.declareProtected( ManagePortal, 'getSite' )
    7.68 +    def getSite( self ):
    7.69 +
    7.70 +        """ See ISetupContext.
    7.71 +        """
    7.72 +        return aq_self(self._site)
    7.73 +
    7.74 +    security.declareProtected( ManagePortal, 'getEncoding' )
    7.75 +    def getEncoding( self ):
    7.76 +
    7.77 +        """ See IImportContext.
    7.78 +        """
    7.79 +        return self._encoding
    7.80 +
    7.81 +    security.declareProtected( ManagePortal, 'readDataFile' )
    7.82 +    def readDataFile( self, filename, subdir=None ):
    7.83 +
    7.84 +        """ See IImportContext.
    7.85 +        """
    7.86 +        if subdir is None:
    7.87 +            full_path = os.path.join( self._profile_path, filename )
    7.88 +        else:
    7.89 +            full_path = os.path.join( self._profile_path, subdir, filename )
    7.90 +
    7.91 +        if not os.path.exists( full_path ):
    7.92 +            return None
    7.93 +
    7.94 +        file = open( full_path, 'rb' )
    7.95 +        result = file.read()
    7.96 +        file.close()
    7.97 +
    7.98 +        return result
    7.99 +
   7.100 +    security.declareProtected( ManagePortal, 'getLastModified' )
   7.101 +    def getLastModified( self, path ):
   7.102 +
   7.103 +        """ See IImportContext.
   7.104 +        """
   7.105 +        full_path = os.path.join( self._profile_path, path )
   7.106 +
   7.107 +        if not os.path.exists( full_path ):
   7.108 +            return None
   7.109 +
   7.110 +        return DateTime( os.path.getmtime( full_path ) )
   7.111 +
   7.112 +    security.declareProtected( ManagePortal, 'isDirectory' )
   7.113 +    def isDirectory( self, path ):
   7.114 +
   7.115 +        """ See IImportContext.
   7.116 +        """
   7.117 +        full_path = os.path.join( self._profile_path, path )
   7.118 +
   7.119 +        if not os.path.exists( full_path ):
   7.120 +            return None
   7.121 +
   7.122 +        return os.path.isdir( full_path )
   7.123 +
   7.124 +    security.declareProtected( ManagePortal, 'listDirectory' )
   7.125 +    def listDirectory( self, path, skip=('CVS', '.svn') ):
   7.126 +
   7.127 +        """ See IImportContext.
   7.128 +        """
   7.129 +        if path is None:
   7.130 +            path = ''
   7.131 +
   7.132 +        full_path = os.path.join( self._profile_path, path )
   7.133 +
   7.134 +        if not os.path.exists( full_path ) or not os.path.isdir( full_path ):
   7.135 +            return None
   7.136 +
   7.137 +        names = os.listdir( full_path )
   7.138 +
   7.139 +        return [ name for name in names if name not in skip ]
   7.140 +
   7.141 +    security.declareProtected( ManagePortal, 'shouldPurge' )
   7.142 +    def shouldPurge( self ):
   7.143 +
   7.144 +        """ See IImportContext.
   7.145 +        """
   7.146 +        return self._should_purge
   7.147 +
   7.148 +InitializeClass( DirectoryImportContext )
   7.149 +
   7.150 +
   7.151 +class DirectoryExportContext( Implicit ):
   7.152 +
   7.153 +    __implements__ = ( IExportContext, )
   7.154 +
   7.155 +    security = ClassSecurityInfo()
   7.156 +
   7.157 +    def __init__( self, tool, profile_path ):
   7.158 +
   7.159 +        self._site = aq_parent( aq_inner( tool ) )
   7.160 +        self._profile_path = profile_path
   7.161 +
   7.162 +    security.declareProtected( ManagePortal, 'getSite' )
   7.163 +    def getSite( self ):
   7.164 +
   7.165 +        """ See ISetupContext.
   7.166 +        """
   7.167 +        return aq_self(self._site)
   7.168 +
   7.169 +    security.declareProtected( ManagePortal, 'writeDataFile' )
   7.170 +    def writeDataFile( self, filename, text, content_type, subdir=None ):
   7.171 +
   7.172 +        """ See IExportContext.
   7.173 +        """
   7.174 +        if subdir is None:
   7.175 +            prefix = self._profile_path
   7.176 +        else:
   7.177 +            prefix = os.path.join( self._profile_path, subdir )
   7.178 +
   7.179 +        full_path = os.path.join( prefix, filename )
   7.180 +
   7.181 +        if not os.path.exists( prefix ):
   7.182 +            os.makedirs( prefix )
   7.183 +
   7.184 +        mode = content_type.startswith( 'text/' ) and 'w' or 'wb'
   7.185 +
   7.186 +        file = open( full_path, mode )
   7.187 +        file.write( text )
   7.188 +        file.close()
   7.189 +
   7.190 +InitializeClass( DirectoryExportContext )
   7.191 +
   7.192 +
   7.193 +class TarballExportContext( Implicit ):
   7.194 +
   7.195 +    __implements__ = ( IExportContext, )
   7.196 +
   7.197 +    security = ClassSecurityInfo()
   7.198 +
   7.199 +    def __init__( self, tool ):
   7.200 +
   7.201 +        self._site = aq_parent( aq_inner( tool ) )
   7.202 +        timestamp = time.gmtime()
   7.203 +        archive_name = ( 'portal_setup-%4d%02d%02d%02d%02d%02d.tar.gz'
   7.204 +                       % timestamp[:6] )
   7.205 +
   7.206 +        self._archive_stream = StringIO()
   7.207 +        self._archive_filename = archive_name
   7.208 +        self._archive = TarFile.open( archive_name, 'w:gz'
   7.209 +                                    , self._archive_stream )
   7.210 +
   7.211 +    security.declareProtected( ManagePortal, 'getSite' )
   7.212 +    def getSite( self ):
   7.213 +
   7.214 +        """ See ISetupContext.
   7.215 +        """
   7.216 +        return aq_self(self._site)
   7.217 +
   7.218 +    security.declareProtected( ManagePortal, 'writeDataFile' )
   7.219 +    def writeDataFile( self, filename, text, content_type, subdir=None ):
   7.220 +
   7.221 +        """ See IExportContext.
   7.222 +        """
   7.223 +        if subdir is not None:
   7.224 +            filename = os.path.join( subdir, filename )
   7.225 +
   7.226 +        stream = StringIO( text )
   7.227 +        info = TarInfo( filename )
   7.228 +        info.size = len( text )
   7.229 +        info.mtime = time.time()
   7.230 +        self._archive.addfile( info, stream )
   7.231 +
   7.232 +    security.declareProtected( ManagePortal, 'getArchive' )
   7.233 +    def getArchive( self ):
   7.234 +
   7.235 +        """ Close the archive, and return it as a big string.
   7.236 +        """
   7.237 +        self._archive.close()
   7.238 +        return self._archive_stream.getvalue()
   7.239 +
   7.240 +    security.declareProtected( ManagePortal, 'getArchiveFilename' )
   7.241 +    def getArchiveFilename( self ):
   7.242 +
   7.243 +        """ Close the archive, and return it as a big string.
   7.244 +        """
   7.245 +        return self._archive_filename
   7.246 +
   7.247 +InitializeClass( TarballExportContext )
   7.248 +
   7.249 +
   7.250 +class SnapshotExportContext( Implicit ):
   7.251 +
   7.252 +    __implements__ = ( IExportContext, )
   7.253 +
   7.254 +    security = ClassSecurityInfo()
   7.255 +
   7.256 +    def __init__( self, tool, snapshot_id ):
   7.257 +
   7.258 +        self._tool = tool = aq_inner( tool )
   7.259 +        self._site = aq_parent( tool )
   7.260 +        self._snapshot_id = snapshot_id
   7.261 +
   7.262 +    security.declareProtected( ManagePortal, 'getSite' )
   7.263 +    def getSite( self ):
   7.264 +
   7.265 +        """ See ISetupContext.
   7.266 +        """
   7.267 +        return aq_self(self._site)
   7.268 +
   7.269 +    security.declareProtected( ManagePortal, 'writeDataFile' )
   7.270 +    def writeDataFile( self, filename, text, content_type, subdir=None ):
   7.271 +
   7.272 +        """ See IExportContext.
   7.273 +        """
   7.274 +        folder = self._ensureSnapshotsFolder( subdir )
   7.275 +
   7.276 +        # TODO: switch on content_type
   7.277 +        ob = self._createObjectByType( filename, text, content_type )
   7.278 +        folder._setObject( str( filename ), ob ) # No Unicode IDs!
   7.279 +
   7.280 +    security.declareProtected( ManagePortal, 'getSnapshotURL' )
   7.281 +    def getSnapshotURL( self ):
   7.282 +
   7.283 +        """ See IExportContext.
   7.284 +        """
   7.285 +        return '%s/%s' % ( self._tool.absolute_url(), self._snapshot_id )
   7.286 +
   7.287 +    security.declareProtected( ManagePortal, 'getSnapshotFolder' )
   7.288 +    def getSnapshotFolder( self ):
   7.289 +
   7.290 +        """ See IExportContext.
   7.291 +        """
   7.292 +        return self._ensureSnapshotsFolder()
   7.293 +
   7.294 +    #
   7.295 +    #   Helper methods
   7.296 +    #
   7.297 +    security.declarePrivate( '_createObjectByType' )
   7.298 +    def _createObjectByType( self, name, body, content_type ):
   7.299 +
   7.300 +        if name.endswith('.py'):
   7.301 +
   7.302 +            ob = PythonScript( name )
   7.303 +            ob.write( body )
   7.304 +
   7.305 +        elif name.endswith('.dtml'):
   7.306 +
   7.307 +            ob = DTMLDocument( '', __name__=name )
   7.308 +            ob.munge( body )
   7.309 +
   7.310 +        elif content_type in ('text/html', 'text/xml' ):
   7.311 +
   7.312 +            ob = ZopePageTemplate( name, str( body )
   7.313 +                                 , content_type=content_type )
   7.314 +
   7.315 +        elif content_type[:6]=='image/':
   7.316 +
   7.317 +            ob=Image( name, '', body, content_type=content_type )
   7.318 +
   7.319 +        else:
   7.320 +            ob=File( name, '', body, content_type=content_type )
   7.321 +
   7.322 +        return ob
   7.323 +
   7.324 +    security.declarePrivate( '_ensureSnapshotsFolder' )
   7.325 +    def _ensureSnapshotsFolder( self, subdir=None ):
   7.326 +
   7.327 +        """ Ensure that the appropriate snapshot folder exists.
   7.328 +        """
   7.329 +        path = [ 'snapshots', self._snapshot_id ]
   7.330 +
   7.331 +        if subdir is not None:
   7.332 +            path.extend( subdir.split( '/' ) )
   7.333 +
   7.334 +        current = self._tool
   7.335 +
   7.336 +        for element in path:
   7.337 +
   7.338 +            if element not in current.objectIds():
   7.339 +                # No Unicode IDs!
   7.340 +                current._setObject( str( element ), Folder( element ) )
   7.341 +
   7.342 +            current = current._getOb( element )
   7.343 +
   7.344 +        return current
   7.345 +
   7.346 +InitializeClass( SnapshotExportContext )
   7.347 +
   7.348 +
   7.349 +class SnapshotImportContext( Implicit ):
   7.350 +
   7.351 +    __implements__ = ( IImportContext, )
   7.352 +
   7.353 +    security = ClassSecurityInfo()
   7.354 +
   7.355 +    def __init__( self
   7.356 +                , tool
   7.357 +                , snapshot_id
   7.358 +                , should_purge=False
   7.359 +                , encoding=None
   7.360 +                ):
   7.361 +
   7.362 +        self._tool = tool = aq_inner( tool )
   7.363 +        self._site = aq_parent( tool )
   7.364 +        self._snapshot_id = snapshot_id
   7.365 +        self._encoding = encoding
   7.366 +        self._should_purge = bool( should_purge )
   7.367 +
   7.368 +    security.declareProtected( ManagePortal, 'getSite' )
   7.369 +    def getSite( self ):
   7.370 +
   7.371 +        """ See ISetupContext.
   7.372 +        """
   7.373 +        return aq_self(self._site)
   7.374 +
   7.375 +    security.declareProtected( ManagePortal, 'getEncoding' )
   7.376 +    def getEncoding( self ):
   7.377 +
   7.378 +        """ Return the encoding used in data files.
   7.379 +
   7.380 +        o Return None if the data should not be encoded.
   7.381 +        """
   7.382 +        return self._encoding
   7.383 +
   7.384 +    security.declareProtected( ManagePortal, 'readDataFile' )
   7.385 +    def readDataFile( self, filename, subdir=None ):
   7.386 +
   7.387 +        """ See IImportContext.
   7.388 +        """
   7.389 +        try:
   7.390 +            snapshot = self._getSnapshotFolder( subdir )
   7.391 +            object = snapshot._getOb( filename )
   7.392 +        except ( AttributeError, KeyError ):
   7.393 +            return None
   7.394 +
   7.395 +        try:
   7.396 +            return object.read()
   7.397 +        except AttributeError:
   7.398 +            return object.manage_FTPget()
   7.399 +
   7.400 +    security.declareProtected( ManagePortal, 'getLastModified' )
   7.401 +    def getLastModified( self, path ):
   7.402 +
   7.403 +        """ See IImportContext.
   7.404 +        """
   7.405 +        try:
   7.406 +            snapshot = self._getSnapshotFolder()
   7.407 +            object = snapshot.restrictedTraverse( path )
   7.408 +        except ( AttributeError, KeyError ):
   7.409 +            return None
   7.410 +        else:
   7.411 +            return object.bobobase_modification_time()
   7.412 +
   7.413 +    security.declareProtected( ManagePortal, 'isDirectory' )
   7.414 +    def isDirectory( self, path ):
   7.415 +
   7.416 +        """ See IImportContext.
   7.417 +        """
   7.418 +        try:
   7.419 +            snapshot = self._getSnapshotFolder()
   7.420 +            object = snapshot.restrictedTraverse( path )
   7.421 +        except ( AttributeError, KeyError ):
   7.422 +            return None
   7.423 +        else:
   7.424 +            folderish = getattr( object, 'isPrincipiaFolderish', False )
   7.425 +            return bool( folderish )
   7.426 +
   7.427 +    security.declareProtected( ManagePortal, 'listDirectory' )
   7.428 +    def listDirectory( self, path, skip=() ):
   7.429 +
   7.430 +        """ See IImportContext.
   7.431 +        """
   7.432 +        try:
   7.433 +            snapshot = self._getSnapshotFolder()
   7.434 +            subdir = snapshot.restrictedTraverse( path )
   7.435 +        except ( AttributeError, KeyError ):
   7.436 +            return None
   7.437 +        else:
   7.438 +            if not getattr( subdir, 'isPrincipiaFolderish', False ):
   7.439 +                return None
   7.440 +
   7.441 +            object_ids = subdir.objectIds()
   7.442 +            return [ x for x in object_ids if x not in skip ]
   7.443 +
   7.444 +    security.declareProtected( ManagePortal, 'shouldPurge' )
   7.445 +    def shouldPurge( self ):
   7.446 +
   7.447 +        """ See IImportContext.
   7.448 +        """
   7.449 +        return self._should_purge
   7.450 +
   7.451 +    #
   7.452 +    #   Helper methods
   7.453 +    #
   7.454 +    security.declarePrivate( '_getSnapshotFolder' )
   7.455 +    def _getSnapshotFolder( self, subdir=None ):
   7.456 +
   7.457 +        """ Return the appropriate snapshot (sub)folder.
   7.458 +        """
   7.459 +        path = [ 'snapshots', self._snapshot_id ]
   7.460 +
   7.461 +        if subdir is not None:
   7.462 +            path.extend( subdir.split( '/' ) )
   7.463 +
   7.464 +        return self._tool.restrictedTraverse( path )
   7.465 +
   7.466 +InitializeClass( SnapshotImportContext )
     8.1 new file mode 100644
     8.2 --- /dev/null
     8.3 +++ b/differ.py
     8.4 @@ -0,0 +1,229 @@
     8.5 +##############################################################################
     8.6 +#
     8.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
     8.8 +#
     8.9 +# This software is subject to the provisions of the Zope Public License,
    8.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
    8.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
    8.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    8.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
    8.14 +# FOR A PARTICULAR PURPOSE.
    8.15 +#
    8.16 +##############################################################################
    8.17 +""" Diff utilities for comparing configurations.
    8.18 +
    8.19 +$Id: differ.py 36457 2004-08-12 15:07:44Z jens $
    8.20 +"""
    8.21 +
    8.22 +from difflib import unified_diff
    8.23 +import re
    8.24 +
    8.25 +from Globals import InitializeClass
    8.26 +from AccessControl import ClassSecurityInfo
    8.27 +
    8.28 +BLANKS_REGEX = re.compile( r'^\s*$' )
    8.29 +
    8.30 +def unidiff( a
    8.31 +           , b
    8.32 +           , filename_a='original'
    8.33 +           , timestamp_a=None
    8.34 +           , filename_b='modified'
    8.35 +           , timestamp_b=None
    8.36 +           , ignore_blanks=False
    8.37 +           ):
    8.38 +    r"""Compare two sequences of lines; generate the resulting delta.
    8.39 +
    8.40 +    Each sequence must contain individual single-line strings
    8.41 +    ending with newlines. Such sequences can be obtained from the
    8.42 +    `readlines()` method of file-like objects.  The delta
    8.43 +    generated also consists of newline-terminated strings, ready
    8.44 +    to be printed as-is via the writeline() method of a file-like
    8.45 +    object.
    8.46 +
    8.47 +    Note that the last line of a file may *not* have a newline;
    8.48 +    this is reported in the same way that GNU diff reports this.
    8.49 +    *This method only supports UNIX line ending conventions.*
    8.50 +
    8.51 +        filename_a and filename_b are used to generate the header,
    8.52 +        allowing other tools to determine what 'files' were used
    8.53 +        to generate this output.
    8.54 +
    8.55 +        timestamp_a and timestamp_b, when supplied, are expected
    8.56 +        to be last-modified timestamps to be inserted in the
    8.57 +        header, as floating point values since the epoch.
    8.58 +
    8.59 +    Example:
    8.60 +
    8.61 +    >>> print ''.join(UniDiffer().compare(
    8.62 +    ...     'one\ntwo\nthree\n'.splitlines(1),
    8.63 +    ...     'ore\ntree\nemu\n'.splitlines(1))),
    8.64 +    +++ original
    8.65 +    --- modified
    8.66 +    @@ -1,3 +1,3 @@
    8.67 +    -one
    8.68 +    +ore
    8.69 +    -two
    8.70 +    -three
    8.71 +    +tree
    8.72 +    +emu
    8.73 +    """
    8.74 +    if isinstance( a, basestring ):
    8.75 +        a = a.splitlines()
    8.76 +
    8.77 +    if isinstance( b, basestring ):
    8.78 +        b = b.splitlines()
    8.79 +
    8.80 +    if ignore_blanks:
    8.81 +        a = [ x for x in a if not BLANKS_REGEX.match( x ) ]
    8.82 +        b = [ x for x in b if not BLANKS_REGEX.match( x ) ]
    8.83 +
    8.84 +    return unified_diff( a
    8.85 +                       , b
    8.86 +                       , filename_a
    8.87 +                       , filename_b
    8.88 +                       , timestamp_a
    8.89 +                       , timestamp_b
    8.90 +                       , lineterm=""
    8.91 +                       )
    8.92 +
    8.93 +class ConfigDiff:
    8.94 +
    8.95 +    security = ClassSecurityInfo()
    8.96 +
    8.97 +    def __init__( self
    8.98 +                , lhs
    8.99 +                , rhs
   8.100 +                , missing_as_empty=False
   8.101 +                , ignore_blanks=False
   8.102 +                , skip=('CVS','.svn')
   8.103 +                ):
   8.104 +        self._lhs = lhs
   8.105 +        self._rhs = rhs
   8.106 +        self._missing_as_empty = missing_as_empty
   8.107 +        self._ignore_blanks=ignore_blanks
   8.108 +        self._skip = skip
   8.109 +
   8.110 +    security.declarePrivate( 'compareDirectories' )
   8.111 +    def compareDirectories( self, subdir=None ):
   8.112 +
   8.113 +        lhs_files = self._lhs.listDirectory( subdir, self._skip )
   8.114 +        if lhs_files is None:
   8.115 +            lhs_files = []
   8.116 +
   8.117 +        rhs_files = self._rhs.listDirectory( subdir, self._skip )
   8.118 +        if rhs_files is None:
   8.119 +            rhs_files = []
   8.120 +
   8.121 +        added = [ f for f in rhs_files if f not in lhs_files ]
   8.122 +        removed = [ f for f in lhs_files if f not in rhs_files ]
   8.123 +        all_files = lhs_files + added
   8.124 +        all_files.sort()
   8.125 +
   8.126 +        result = []
   8.127 +
   8.128 +        for filename in all_files:
   8.129 +
   8.130 +            if subdir is None:
   8.131 +                pathname = filename
   8.132 +            else:
   8.133 +                pathname = '%s/%s' % ( subdir, filename )
   8.134 +
   8.135 +            if filename not in added:
   8.136 +                isDirectory = self._lhs.isDirectory( pathname )
   8.137 +            else:
   8.138 +                isDirectory = self._rhs.isDirectory( pathname )
   8.139 +
   8.140 +            if not self._missing_as_empty and filename in removed:
   8.141 +
   8.142 +                if isDirectory:
   8.143 +                    result.append( '** Directory %s removed\n' % pathname )
   8.144 +                    result.extend( self.compareDirectories( pathname ) )
   8.145 +                else:
   8.146 +                    result.append( '** File %s removed\n' % pathname )
   8.147 +
   8.148 +            elif not self._missing_as_empty and filename in added:
   8.149 +
   8.150 +                if isDirectory:
   8.151 +                    result.append( '** Directory %s added\n' % pathname )
   8.152 +                    result.extend( self.compareDirectories( pathname ) )
   8.153 +                else:
   8.154 +                    result.append( '** File %s added\n' % pathname )
   8.155 +
   8.156 +            elif isDirectory:
   8.157 +
   8.158 +                result.extend( self.compareDirectories( pathname ) )
   8.159 +
   8.160 +                if ( filename not in added + removed and
   8.161 +                    not self._rhs.isDirectory( pathname ) ):
   8.162 +
   8.163 +                    result.append( '** Directory %s replaced with a file of '
   8.164 +                                   'the same name\n' % pathname )
   8.165 +
   8.166 +                    if self._missing_as_empty:
   8.167 +                        result.extend( self.compareFiles( filename, subdir ) )
   8.168 +            else:
   8.169 +                if ( filename not in added + removed and
   8.170 +                     self._rhs.isDirectory( pathname ) ):
   8.171 +
   8.172 +                    result.append( '** File %s replaced with a directory of '
   8.173 +                                   'the same name\n' % pathname )
   8.174 +
   8.175 +                    if self._missing_as_empty:
   8.176 +                        result.extend( self.compareFiles( filename, subdir ) )
   8.177 +
   8.178 +                    result.extend( self.compareDirectories( pathname ) )
   8.179 +                else:
   8.180 +                    result.extend( self.compareFiles( filename, subdir ) )
   8.181 +
   8.182 +        return result
   8.183 +
   8.184 +    security.declarePrivate( 'compareFiles' )
   8.185 +    def compareFiles( self, filename, subdir=None ):
   8.186 +
   8.187 +        if subdir is None:
   8.188 +            path = filename
   8.189 +        else:
   8.190 +            path = '%s/%s' % ( subdir, filename )
   8.191 +
   8.192 +        lhs_file = self._lhs.readDataFile( filename, subdir )
   8.193 +        lhs_time = self._lhs.getLastModified( path )
   8.194 +
   8.195 +        if lhs_file is None:
   8.196 +            assert self._missing_as_empty
   8.197 +            lhs_file = ''
   8.198 +            lhs_time = 0
   8.199 +
   8.200 +        rhs_file = self._rhs.readDataFile( filename, subdir )
   8.201 +        rhs_time = self._rhs.getLastModified( path )
   8.202 +
   8.203 +        if rhs_file is None:
   8.204 +            assert self._missing_as_empty
   8.205 +            rhs_file = ''
   8.206 +            rhs_time = 0
   8.207 +
   8.208 +        if lhs_file == rhs_file:
   8.209 +            diff_lines = []
   8.210 +        else:
   8.211 +            diff_lines = unidiff( lhs_file
   8.212 +                                , rhs_file
   8.213 +                                , filename_a=path
   8.214 +                                , timestamp_a=lhs_time
   8.215 +                                , filename_b=path
   8.216 +                                , timestamp_b=rhs_time
   8.217 +                                , ignore_blanks=self._ignore_blanks
   8.218 +                                )
   8.219 +            diff_lines = list( diff_lines ) # generator
   8.220 +
   8.221 +        if len( diff_lines ) == 0: # No *real* difference found
   8.222 +            return []
   8.223 +
   8.224 +        diff_lines.insert( 0, 'Index: %s' % path )
   8.225 +        diff_lines.insert( 1, '=' * 67 )
   8.226 +
   8.227 +        return diff_lines
   8.228 +
   8.229 +    security.declarePrivate( 'compare' )
   8.230 +    def compare( self ):
   8.231 +        return '\n'.join( self.compareDirectories() )
   8.232 +
   8.233 +InitializeClass( ConfigDiff )
     9.1 new file mode 100644
     9.2 --- /dev/null
     9.3 +++ b/exceptions.py
     9.4 @@ -0,0 +1,22 @@
     9.5 +##############################################################################
     9.6 +#
     9.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
     9.8 +#
     9.9 +# This software is subject to the provisions of the Zope Public License,
    9.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
    9.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
    9.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    9.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
    9.14 +# FOR A PARTICULAR PURPOSE.
    9.15 +#
    9.16 +##############################################################################
    9.17 +""" CMFSetup product exceptions.
    9.18 +
    9.19 +$Id: exceptions.py 36701 2004-12-14 19:34:49Z yuppie $
    9.20 +"""
    9.21 +
    9.22 +from AccessControl import ModuleSecurityInfo
    9.23 +security = ModuleSecurityInfo('Products.CMFSetup.exceptions')
    9.24 +
    9.25 +security.declarePublic('BadRequest')
    9.26 +from Products.CMFCore.exceptions import BadRequest
    10.1 new file mode 100644
    10.2 --- /dev/null
    10.3 +++ b/factory.py
    10.4 @@ -0,0 +1,76 @@
    10.5 +##############################################################################
    10.6 +#
    10.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    10.8 +#
    10.9 +# This software is subject to the provisions of the Zope Public License,
   10.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   10.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   10.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   10.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   10.14 +# FOR A PARTICULAR PURPOSE.
   10.15 +#
   10.16 +##############################################################################
   10.17 +""" Configured site factory implementation.
   10.18 +
   10.19 +$Id: factory.py 36896 2005-04-05 15:17:18Z yuppie $
   10.20 +"""
   10.21 +
   10.22 +from Products.PageTemplates.PageTemplateFile import PageTemplateFile
   10.23 +
   10.24 +from Products.CMFCore.utils import getToolByName
   10.25 +from Products.CMFDefault.Portal import CMFSite
   10.26 +
   10.27 +from interfaces import EXTENSION
   10.28 +from registry import _profile_registry as profile_registry
   10.29 +from tool import SetupTool
   10.30 +from utils import _wwwdir
   10.31 +
   10.32 +def addConfiguredSiteForm( dispatcher ):
   10.33 +
   10.34 +    """ Wrap the PTF in 'dispatcher', including 'profile_registry' in options.
   10.35 +    """
   10.36 +    wrapped = PageTemplateFile( 'siteAddForm', _wwwdir ).__of__( dispatcher )
   10.37 +
   10.38 +    base_profiles = []
   10.39 +    extension_profiles = []
   10.40 +
   10.41 +    for info in profile_registry.listProfileInfo():
   10.42 +        if info.get('type') == EXTENSION:
   10.43 +            extension_profiles.append(info)
   10.44 +        else:
   10.45 +            base_profiles.append(info)
   10.46 +
   10.47 +    return wrapped( base_profiles=tuple(base_profiles),
   10.48 +                    extension_profiles =tuple(extension_profiles) )
   10.49 +
   10.50 +def addConfiguredSite( dispatcher
   10.51 +                     , site_id
   10.52 +                     , profile_id
   10.53 +                     , snapshot=True
   10.54 +                     , RESPONSE=None 
   10.55 +                     , extension_ids=()
   10.56 +                     ):
   10.57 +
   10.58 +    """ Add a CMFSite to 'dispatcher', configured according to 'profile_id'.
   10.59 +    """
   10.60 +    site = CMFSite( site_id )
   10.61 +    dispatcher._setObject( site_id, site )
   10.62 +    site = dispatcher._getOb( site_id )
   10.63 +
   10.64 +    setup_tool = SetupTool()
   10.65 +    site._setObject( 'portal_setup', setup_tool )
   10.66 +    setup_tool = getToolByName( site, 'portal_setup' )
   10.67 +
   10.68 +    setup_tool.setImportContext( 'profile-%s' % profile_id )
   10.69 +    setup_tool.runAllImportSteps()
   10.70 +    for extension_id in extension_ids:
   10.71 +        setup_tool.setImportContext( 'profile-%s' % extension_id )
   10.72 +        setup_tool.runAllImportSteps()
   10.73 +    setup_tool.setImportContext( 'profile-%s' % profile_id )
   10.74 +
   10.75 +    if snapshot is True:
   10.76 +        setup_tool.createSnapshot( 'initial_configuration' )
   10.77 +
   10.78 +    if RESPONSE is not None:
   10.79 +        RESPONSE.redirect( '%s/manage_main?update_menu=1'
   10.80 +                         % dispatcher.absolute_url() )
    11.1 new file mode 100644
    11.2 --- /dev/null
    11.3 +++ b/interfaces.py
    11.4 @@ -0,0 +1,508 @@
    11.5 +##############################################################################
    11.6 +#
    11.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    11.8 +#
    11.9 +# This software is subject to the provisions of the Zope Public License,
   11.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   11.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   11.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   11.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   11.14 +# FOR A PARTICULAR PURPOSE.
   11.15 +#
   11.16 +##############################################################################
   11.17 +""" CMFSetup product interfaces
   11.18 +
   11.19 +$Id: interfaces.py 37351 2005-07-20 21:16:59Z jens $
   11.20 +"""
   11.21 +
   11.22 +from Interface import Interface
   11.23 +
   11.24 +
   11.25 +BASE, EXTENSION = range(2)
   11.26 +
   11.27 +
   11.28 +class IPseudoInterface( Interface ):
   11.29 +
   11.30 +    """ API documentation;  not testable / enforceable.
   11.31 +    """
   11.32 +
   11.33 +class ISetupContext( Interface ):
   11.34 +
   11.35 +    """ Context used for export / import plugins.
   11.36 +    """
   11.37 +    def getSite():
   11.38 +
   11.39 +        """ Return the site object being configured / dumped.
   11.40 +        """
   11.41 +
   11.42 +class IImportContext( ISetupContext ):
   11.43 +
   11.44 +    def getEncoding():
   11.45 +
   11.46 +        """ Get the encoding used for configuration data within the site.
   11.47 +
   11.48 +        o Return None if the data should not be encoded.
   11.49 +        """
   11.50 +
   11.51 +    def readDataFile( filename, subdir=None ):
   11.52 +
   11.53 +        """ Search the current configuration for the requested file.
   11.54 +
   11.55 +        o 'filename' is the name (without path elements) of the file.
   11.56 +
   11.57 +        o 'subdir' is an optional subdirectory;  if not supplied, search
   11.58 +          only the "root" directory.
   11.59 +
   11.60 +        o Return the file contents as a string, or None if the
   11.61 +          file cannot be found.
   11.62 +        """
   11.63 +
   11.64 +    def getLastModified( path ):
   11.65 +
   11.66 +        """ Return the modification timestamp of the item at 'path'.
   11.67 +
   11.68 +        o Result will be a DateTime instance.
   11.69 +
   11.70 +        o Search profiles in the configuration in order.
   11.71 +
   11.72 +        o If the context is filesystem based, return the 'stat' timestamp
   11.73 +          of the file / directory to which 'path' points.
   11.74 +
   11.75 +        o If the context is ZODB-based, return the Zope modification time
   11.76 +          of the object to which 'path' points.
   11.77 +
   11.78 +        o Return None if 'path' does not point to any object.
   11.79 +        """
   11.80 +
   11.81 +    def isDirectory( path ):
   11.82 +
   11.83 +        """ Test whether path points to a directory / folder.
   11.84 +
   11.85 +        o If the context is filesystem based, check that 'path' points to
   11.86 +          a subdirectory within the "root" directory.
   11.87 +
   11.88 +        o If the context is ZODB-based, check that 'path' points to a
   11.89 +          "container" under the context's tool.
   11.90 +
   11.91 +        o Return None if 'path' does not resolve;  otherwise, return a
   11.92 +          bool.
   11.93 +        """
   11.94 +
   11.95 +    def listDirectory( path, skip=('CVS', '.svn') ):
   11.96 +
   11.97 +        """ List IDs of the contents of a  directory / folder.
   11.98 +
   11.99 +        o Omit names in 'skip'.
  11.100 +
  11.101 +        o If 'path' does not point to a directory / folder, return None.
  11.102 +        """
  11.103 +
  11.104 +    def shouldPurge():
  11.105 +
  11.106 +        """ When installing, should the existing setup be purged?
  11.107 +        """
  11.108 +
  11.109 +class IImportPlugin( IPseudoInterface ):
  11.110 +
  11.111 +    """ Signature for callables used to import portions of site configuration.
  11.112 +    """
  11.113 +    def __call__( context ):
  11.114 +
  11.115 +        """ Perform the setup step.
  11.116 +
  11.117 +        o Return a message describing the work done.
  11.118 +
  11.119 +        o 'context' must implement IImportContext.
  11.120 +        """
  11.121 +
  11.122 +class IExportContext( ISetupContext ):
  11.123 +
  11.124 +    def writeDataFile( filename, text, content_type, subdir=None ):
  11.125 +
  11.126 +        """ Write data into the specified location.
  11.127 +
  11.128 +        o 'filename' is the unqualified name of the file.
  11.129 +
  11.130 +        o 'text' is the content of the file.
  11.131 +
  11.132 +        o 'content_type' is the MIMEtype of the file.
  11.133 +
  11.134 +        o 'subdir', if passed, is a path to a subdirectory / folder in
  11.135 +          which to write the file;  if not passed, write the file to the
  11.136 +          "root" of the target.
  11.137 +        """
  11.138 +
  11.139 +class IExportPlugin( IPseudoInterface ):
  11.140 +
  11.141 +    """ Signature for callables used to export portions of site configuration.
  11.142 +    """
  11.143 +    def __call__( context ):
  11.144 +
  11.145 +        """ Write export data for the site wrapped by context.
  11.146 +
  11.147 +        o Return a message describing the work done.
  11.148 +
  11.149 +        o 'context' must implement IExportContext.  The plugin will use
  11.150 +          its 'writeDataFile' method for each file to be exported.
  11.151 +        """
  11.152 +
  11.153 +class IStepRegistry( Interface ):
  11.154 +
  11.155 +    """ Base interface for step registries.
  11.156 +    """
  11.157 +    def listSteps():
  11.158 +
  11.159 +        """ Return a sequence of IDs of registered steps.
  11.160 +
  11.161 +        o Order is not significant.
  11.162 +        """
  11.163 +
  11.164 +    def listStepMetadata():
  11.165 +
  11.166 +        """ Return a sequence of mappings describing registered steps.
  11.167 +
  11.168 +        o Mappings will be ordered alphabetically.
  11.169 +        """
  11.170 +
  11.171 +    def getStepMetadata( key, default=None ):
  11.172 +
  11.173 +        """ Return a mapping of metadata for the step identified by 'key'.
  11.174 +
  11.175 +        o Return 'default' if no such step is registered.
  11.176 +
  11.177 +        o The 'handler' metadata is available via 'getStep'.
  11.178 +        """
  11.179 +
  11.180 +    def generateXML():
  11.181 +
  11.182 +        """ Return a round-trippable XML representation of the registry.
  11.183 +
  11.184 +        o 'handler' values are serialized using their dotted names.
  11.185 +        """
  11.186 +
  11.187 +    def parseXML( text ):
  11.188 +
  11.189 +        """ Parse 'text'.
  11.190 +        """
  11.191 +
  11.192 +class IImportStepRegistry( IStepRegistry ):
  11.193 +
  11.194 +    """ API for import step registry.
  11.195 +    """
  11.196 +    def sortSteps():
  11.197 +
  11.198 +        """ Return a sequence of registered step IDs
  11.199 +
  11.200 +        o Sequence is sorted topologically by dependency, with the dependent
  11.201 +          steps *after* the steps they depend on.
  11.202 +        """
  11.203 +
  11.204 +    def checkComplete():
  11.205 +
  11.206 +        """ Return a sequence of ( node, edge ) tuples for unsatisifed deps.
  11.207 +        """
  11.208 +
  11.209 +    def getStep( key, default=None ):
  11.210 +
  11.211 +        """ Return the IImportPlugin registered for 'key'.
  11.212 +
  11.213 +        o Return 'default' if no such step is registered.
  11.214 +        """
  11.215 +
  11.216 +    def registerStep( id
  11.217 +                    , version
  11.218 +                    , handler
  11.219 +                    , dependencies=()
  11.220 +                    , title=None
  11.221 +                    , description=None
  11.222 +                    ):
  11.223 +        """ Register a setup step.
  11.224 +
  11.225 +        o 'id' is a unique name for this step,
  11.226 +
  11.227 +        o 'version' is a string for comparing versions, it is preferred to
  11.228 +          be a yyyy/mm/dd-ii formatted string (date plus two-digit
  11.229 +          ordinal).  when comparing two version strings, the version with
  11.230 +          the lower sort order is considered the older version.
  11.231 +
  11.232 +          - Newer versions of a step supplant older ones.
  11.233 +
  11.234 +          - Attempting to register an older one after a newer one results
  11.235 +            in a KeyError.
  11.236 +
  11.237 +        o 'handler' should implement IImportPlugin.
  11.238 +
  11.239 +        o 'dependencies' is a tuple of step ids which have to run before
  11.240 +          this step in order to be able to run at all. Registration of
  11.241 +          steps that have unmet dependencies are deferred until the
  11.242 +          dependencies have been registered.
  11.243 +
  11.244 +        o 'title' is a one-line UI description for this step.
  11.245 +          If None, the first line of the documentation string of the handler
  11.246 +          is used, or the id if no docstring can be found.
  11.247 +
  11.248 +        o 'description' is a one-line UI description for this step.
  11.249 +          If None, the remaining line of the documentation string of
  11.250 +          the handler is used, or default to ''.
  11.251 +        """
  11.252 +
  11.253 +class IExportStepRegistry( IStepRegistry ):
  11.254 +
  11.255 +    """ API for export step registry.
  11.256 +    """
  11.257 +    def getStep( key, default=None ):
  11.258 +
  11.259 +        """ Return the IExportPlugin registered for 'key'.
  11.260 +
  11.261 +        o Return 'default' if no such step is registered.
  11.262 +        """
  11.263 +
  11.264 +    def registerStep( id, handler, title=None, description=None ):
  11.265 +
  11.266 +        """ Register an export step.
  11.267 +
  11.268 +        o 'id' is the unique identifier for this step
  11.269 +
  11.270 +        o 'handler' should implement IExportPlugin.
  11.271 +
  11.272 +        o 'title' is a one-line UI description for this step.
  11.273 +          If None, the first line of the documentation string of the step
  11.274 +          is used, or the id if no docstring can be found.
  11.275 +
  11.276 +        o 'description' is a one-line UI description for this step.
  11.277 +          If None, the remaining line of the documentation string of
  11.278 +          the step is used, or default to ''.
  11.279 +        """
  11.280 +
  11.281 +class IToolsetRegistry( Interface ):
  11.282 +
  11.283 +    """ API for toolset registry.
  11.284 +    """
  11.285 +    def listForbiddenTools():
  11.286 +
  11.287 +        """ Return a list of IDs of tools which must be removed, if present.
  11.288 +        """
  11.289 +
  11.290 +    def addForbiddenTool(tool_id ):
  11.291 +
  11.292 +        """ Add 'tool_id' to the list of forbidden tools.
  11.293 +
  11.294 +        o Raise KeyError if 'tool_id' is already in the list.
  11.295 +
  11.296 +        o Raise ValueError if 'tool_id' is in the "required" list.
  11.297 +        """
  11.298 +
  11.299 +    def listRequiredTools():
  11.300 +
  11.301 +        """ Return a list of IDs of tools which must be present.
  11.302 +        """
  11.303 +
  11.304 +    def getRequiredToolInfo( tool_id ):
  11.305 +
  11.306 +        """ Return a mapping describing a partiuclar required tool.
  11.307 +
  11.308 +        o Keys include:
  11.309 +
  11.310 +          'id' -- the ID of the tool
  11.311 +
  11.312 +          'class' -- a dotted path to its class
  11.313 +
  11.314 +        o Raise KeyError if 'tool_id' id not a known tool.
  11.315 +        """
  11.316 +
  11.317 +    def listRequiredToolInfo():
  11.318 +
  11.319 +        """ Return a list of IDs of tools which must be present.
  11.320 +        """
  11.321 +
  11.322 +    def addRequiredTool( tool_id, dotted_name ):
  11.323 +
  11.324 +        """ Add a tool to our "required" list.
  11.325 +
  11.326 +        o 'tool_id' is the tool's ID.
  11.327 +
  11.328 +        o 'dotted_name' is a dotted (importable) name of the tool's class.
  11.329 +
  11.330 +        o Raise KeyError if we have already registered a class for 'tool_id'.
  11.331 +
  11.332 +        o Raise ValueError if 'tool_id' is in the "forbidden" list.
  11.333 +        """
  11.334 +
  11.335 +class IProfileRegistry( Interface ):
  11.336 +
  11.337 +    """ API for profile registry.
  11.338 +    """
  11.339 +    def getProfileInfo( profile_id ):
  11.340 +
  11.341 +        """ Return a mapping describing a registered filesystem profile.
  11.342 +
  11.343 +        o Keys include:
  11.344 +
  11.345 +          'id' -- the ID of the profile
  11.346 +
  11.347 +          'title' -- its title
  11.348 +
  11.349 +          'description' -- a textual description of the profile
  11.350 +
  11.351 +          'path' -- a path to the profile on the filesystem.
  11.352 +
  11.353 +          'product' -- the name of the product to which 'path' is
  11.354 +             relative (None for absolute paths).
  11.355 +
  11.356 +          'type' -- either BASE or EXTENSION
  11.357 +        """
  11.358 +
  11.359 +    def listProfiles():
  11.360 +
  11.361 +        """ Return a list of IDs for registered profiles.
  11.362 +        """
  11.363 +
  11.364 +    def listProfileInfo():
  11.365 +
  11.366 +        """ Return a list of mappings describing registered profiles.
  11.367 +
  11.368 +        o See 'getProfileInfo' for a description of the mappings' keys.
  11.369 +        """
  11.370 +
  11.371 +    def registerProfile( name
  11.372 +                       , title
  11.373 +                       , description
  11.374 +                       , path
  11.375 +                       , product=None
  11.376 +                       , profile_type=BASE
  11.377 +                       ):
  11.378 +        """ Add a new profile to the registry.
  11.379 +
  11.380 +        o If an existing profile is already registered for 'product:name',
  11.381 +          raise KeyError.
  11.382 +
  11.383 +        o If 'product' is passed, then 'path' should be interpreted as
  11.384 +          relative to the corresponding product directory.
  11.385 +        """
  11.386 +
  11.387 +class ISetupTool( Interface ):
  11.388 +
  11.389 +    """ API for SetupTool.
  11.390 +    """
  11.391 +
  11.392 +    def getEncoding():
  11.393 +
  11.394 +        """ Get the encoding used for configuration data within the site.
  11.395 +
  11.396 +        o Return None if the data should not be encoded.
  11.397 +        """
  11.398 +
  11.399 +    def getImportContextID():
  11.400 +
  11.401 +        """ Get the ID of the active import context.
  11.402 +        """
  11.403 +
  11.404 +    def setImportContext( context_id ):
  11.405 +
  11.406 +        """ Set the ID of the active import context and update the registries.
  11.407 +        """
  11.408 +
  11.409 +    def getImportStepRegistry():
  11.410 +
  11.411 +        """ Return the IImportStepRegistry for the tool.
  11.412 +        """
  11.413 +
  11.414 +    def getExportStepRegistry():
  11.415 +
  11.416 +        """ Return the IExportStepRegistry for the tool.
  11.417 +        """
  11.418 +
  11.419 +    def getToolsetRegistry():
  11.420 +
  11.421 +        """ Return the IToolsetRegistry for the tool.
  11.422 +        """
  11.423 +
  11.424 +    def runImportStep( step_id, run_dependencies=True, purge_old=None ):
  11.425 +
  11.426 +        """ Execute a given setup step
  11.427 +
  11.428 +        o 'step_id' is the ID of the step to run.
  11.429 +
  11.430 +        o If 'purge_old' is True, then run the step after purging any
  11.431 +          "old" setup first (this is the responsibility of the step,
  11.432 +          which must check the context we supply).
  11.433 +
  11.434 +        o If 'run_dependencies' is True, then run any out-of-date
  11.435 +          dependency steps first.
  11.436 +
  11.437 +        o Return a mapping, with keys:
  11.438 +
  11.439 +          'steps' -- a sequence of IDs of the steps run.
  11.440 +
  11.441 +          'messages' -- a dictionary holding messages returned from each
  11.442 +            step
  11.443 +        """
  11.444 +
  11.445 +    def runAllImportSteps( purge_old=None ):
  11.446 +
  11.447 +        """ Run all setup steps in dependency order.
  11.448 +
  11.449 +        o If 'purge_old' is True, then run each step after purging any
  11.450 +          "old" setup first (this is the responsibility of the step,
  11.451 +          which must check the context we supply).
  11.452 +
  11.453 +        o Return a mapping, with keys:
  11.454 +
  11.455 +          'steps' -- a sequence of IDs of the steps run.
  11.456 +
  11.457 +          'messages' -- a dictionary holding messages returned from each
  11.458 +            step
  11.459 +        """
  11.460 +
  11.461 +    def runExportStep( step_id ):
  11.462 +
  11.463 +        """ Generate a tarball containing artifacts from one export step.
  11.464 +
  11.465 +        o 'step_id' identifies the export step.
  11.466 +
  11.467 +        o Return a mapping, with keys:
  11.468 +
  11.469 +          'steps' -- a sequence of IDs of the steps run.
  11.470 +
  11.471 +          'messages' -- a dictionary holding messages returned from each
  11.472 +            step
  11.473 +
  11.474 +          'tarball' -- the stringified tar-gz data.
  11.475 +        """
  11.476 +
  11.477 +    def runAllExportSteps():
  11.478 +
  11.479 +        """ Generate a tarball containing artifacts from all export steps.
  11.480 +
  11.481 +        o Return a mapping, with keys:
  11.482 +
  11.483 +          'steps' -- a sequence of IDs of the steps run.
  11.484 +
  11.485 +          'messages' -- a dictionary holding messages returned from each
  11.486 +            step
  11.487 +
  11.488 +          'tarball' -- the stringified tar-gz data.
  11.489 +        """
  11.490 +
  11.491 +    def createSnapshot( snapshot_id ):
  11.492 +
  11.493 +        """ Create a snapshot folder using all steps.
  11.494 +
  11.495 +        o 'snapshot_id' is the ID of the new folder.
  11.496 +        """
  11.497 +
  11.498 +    def compareConfigurations( lhs_context
  11.499 +                             , rhs_context
  11.500 +                             , missing_as_empty=False
  11.501 +                             , ignore_whitespace=False
  11.502 +                             ):
  11.503 +        """ Compare two configurations.
  11.504 +
  11.505 +        o 'lhs_context' and 'rhs_context' must implement IImportContext.
  11.506 +
  11.507 +        o If 'missing_as_empty', then compare files not present as though
  11.508 +          they were zero-length;  otherwise, omit such files.
  11.509 +
  11.510 +        o If 'ignore_whitespace', then suppress diffs due only to whitespace
  11.511 +          (c.f:  'diff -wbB')
  11.512 +        """
    12.1 new file mode 100644
    12.2 --- /dev/null
    12.3 +++ b/permissions.py
    12.4 @@ -0,0 +1,18 @@
    12.5 +##############################################################################
    12.6 +#
    12.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    12.8 +#
    12.9 +# This software is subject to the provisions of the Zope Public License,
   12.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   12.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   12.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   12.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   12.14 +# FOR A PARTICULAR PURPOSE.
   12.15 +#
   12.16 +##############################################################################
   12.17 +""" CMFSetup product permissions.
   12.18 +
   12.19 +$Id: permissions.py 36457 2004-08-12 15:07:44Z jens $
   12.20 +"""
   12.21 +
   12.22 +from Products.CMFCore.permissions import ManagePortal
    13.1 new file mode 100644
    13.2 --- /dev/null
    13.3 +++ b/properties.py
    13.4 @@ -0,0 +1,97 @@
    13.5 +##############################################################################
    13.6 +#
    13.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    13.8 +#
    13.9 +# This software is subject to the provisions of the Zope Public License,
   13.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   13.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   13.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   13.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   13.14 +# FOR A PARTICULAR PURPOSE.
   13.15 +#
   13.16 +##############################################################################
   13.17 +""" Site properties setup handlers.
   13.18 +
   13.19 +$Id: properties.py 36799 2005-03-13 16:22:55Z yuppie $
   13.20 +"""
   13.21 +
   13.22 +from AccessControl import ClassSecurityInfo
   13.23 +from Globals import InitializeClass
   13.24 +from Products.PageTemplates.PageTemplateFile import PageTemplateFile
   13.25 +
   13.26 +from permissions import ManagePortal
   13.27 +from utils import _xmldir
   13.28 +from utils import ConfiguratorBase
   13.29 +from utils import DEFAULT, KEY
   13.30 +
   13.31 +
   13.32 +#
   13.33 +#   Configurator entry points
   13.34 +#
   13.35 +_FILENAME = 'properties.xml'
   13.36 +
   13.37 +def importSiteProperties(context):
   13.38 +    """ Import site properties from an XML file.
   13.39 +    """
   13.40 +    site = context.getSite()
   13.41 +    encoding = context.getEncoding()
   13.42 +
   13.43 +    if context.shouldPurge():
   13.44 +
   13.45 +        for prop_map in site._propertyMap():
   13.46 +            prop_id = prop_map['id']
   13.47 +            if 'd' in prop_map.get('mode', 'wd') and \
   13.48 +                    prop_id not in ('title', 'description'):
   13.49 +                site._delProperty(prop_id)
   13.50 +            else:
   13.51 +                if prop_map.get('type') == 'multiple selection':
   13.52 +                    prop_value = ()
   13.53 +                else:
   13.54 +                    prop_value = ''
   13.55 +                site._updateProperty(prop_id, prop_value)
   13.56 +
   13.57 +    xml = context.readDataFile(_FILENAME)
   13.58 +    if xml is None:
   13.59 +        return 'Site properties: Nothing to import.'
   13.60 +
   13.61 +    spc = SitePropertiesConfigurator(site, encoding)
   13.62 +    site_info = spc.parseXML(xml)
   13.63 +
   13.64 +    for prop_info in site_info['properties']:
   13.65 +        spc.initProperty(site, prop_info)
   13.66 +
   13.67 +    return 'Site properties imported.'
   13.68 +
   13.69 +def exportSiteProperties(context):
   13.70 +    """ Export site properties as an XML file.
   13.71 +    """
   13.72 +    site = context.getSite()
   13.73 +    spc = SitePropertiesConfigurator(site).__of__(site)
   13.74 +
   13.75 +    xml = spc.generateXML()
   13.76 +    context.writeDataFile(_FILENAME, xml, 'text/xml')
   13.77 +
   13.78 +    return 'Site properties exported.'
   13.79 +
   13.80 +
   13.81 +class SitePropertiesConfigurator(ConfiguratorBase):
   13.82 +    """ Synthesize XML description of site's properties.
   13.83 +    """
   13.84 +    security = ClassSecurityInfo()
   13.85 +
   13.86 +    security.declareProtected(ManagePortal, 'listSiteInfos')
   13.87 +    def listSiteInfos(self):
   13.88 +        """ Get a sequence of mappings for site properties.
   13.89 +        """
   13.90 +        return tuple( [ self._extractProperty(self._site, prop_map)
   13.91 +                        for prop_map in self._site._propertyMap() ] )
   13.92 +
   13.93 +    def _getExportTemplate(self):
   13.94 +
   13.95 +        return PageTemplateFile('spcExport.xml', _xmldir)
   13.96 +
   13.97 +    def _getImportMapping(self):
   13.98 +
   13.99 +        return { 'site': { 'property': {KEY: 'properties', DEFAULT: () } } }
  13.100 +
  13.101 +InitializeClass(SitePropertiesConfigurator)
    14.1 new file mode 100644
    14.2 --- /dev/null
    14.3 +++ b/registry.py
    14.4 @@ -0,0 +1,753 @@
    14.5 +##############################################################################
    14.6 +#
    14.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    14.8 +#
    14.9 +# This software is subject to the provisions of the Zope Public License,
   14.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   14.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   14.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   14.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   14.14 +# FOR A PARTICULAR PURPOSE.
   14.15 +#
   14.16 +##############################################################################
   14.17 +""" Classes:  ImportStepRegistry, ExportStepRegistry
   14.18 +
   14.19 +$Id: registry.py 36896 2005-04-05 15:17:18Z yuppie $
   14.20 +"""
   14.21 +
   14.22 +from xml.sax import parseString
   14.23 +
   14.24 +from AccessControl import ClassSecurityInfo
   14.25 +from Acquisition import Implicit
   14.26 +from Globals import InitializeClass
   14.27 +from Products.PageTemplates.PageTemplateFile import PageTemplateFile
   14.28 +
   14.29 +from interfaces import BASE
   14.30 +from interfaces import IImportStepRegistry
   14.31 +from interfaces import IExportStepRegistry
   14.32 +from interfaces import IToolsetRegistry
   14.33 +from interfaces import IProfileRegistry
   14.34 +from permissions import ManagePortal
   14.35 +from utils import HandlerBase
   14.36 +from utils import _xmldir
   14.37 +from utils import _getDottedName
   14.38 +from utils import _resolveDottedName
   14.39 +from utils import _extractDocstring
   14.40 +
   14.41 +
   14.42 +class ImportStepRegistry( Implicit ):
   14.43 +
   14.44 +    """ Manage knowledge about steps to create / configure site.
   14.45 +
   14.46 +    o Steps are composed together to define a site profile.
   14.47 +    """
   14.48 +    __implements__ = ( IImportStepRegistry, )
   14.49 +
   14.50 +    security = ClassSecurityInfo()
   14.51 +
   14.52 +    def __init__( self ):
   14.53 +
   14.54 +        self.clear()
   14.55 +
   14.56 +    security.declareProtected( ManagePortal, 'listSteps' )
   14.57 +    def listSteps( self ):
   14.58 +
   14.59 +        """ Return a sequence of IDs of registered steps.
   14.60 +
   14.61 +        o Order is not significant.
   14.62 +        """
   14.63 +        return self._registered.keys()
   14.64 +
   14.65 +    security.declareProtected( ManagePortal, 'sortSteps' )
   14.66 +    def sortSteps( self ):
   14.67 +
   14.68 +        """ Return a sequence of registered step IDs
   14.69 +
   14.70 +        o Sequence is sorted topologically by dependency, with the dependent
   14.71 +          steps *after* the steps they depend on.
   14.72 +        """
   14.73 +        return self._computeTopologicalSort()
   14.74 +
   14.75 +    security.declareProtected( ManagePortal, 'checkComplete' )
   14.76 +    def checkComplete( self ):
   14.77 +
   14.78 +        """ Return a sequence of ( node, edge ) tuples for unsatisifed deps.
   14.79 +        """
   14.80 +        result = []
   14.81 +        seen = {}
   14.82 +
   14.83 +        graph = self._computeTopologicalSort()
   14.84 +
   14.85 +        for node in graph:
   14.86 +
   14.87 +            dependencies = self.getStepMetadata( node )[ 'dependencies' ]
   14.88 +
   14.89 +            for dependency in dependencies:
   14.90 +
   14.91 +                if seen.get( dependency ) is None:
   14.92 +                    result.append( ( node, dependency ) )
   14.93 +
   14.94 +            seen[ node ] = 1
   14.95 +
   14.96 +        return result
   14.97 +
   14.98 +    security.declareProtected( ManagePortal, 'getStepMetadata' )
   14.99 +    def getStepMetadata( self, key, default=None ):
  14.100 +
  14.101 +        """ Return a mapping of metadata for the step identified by 'key'.
  14.102 +
  14.103 +        o Return 'default' if no such step is registered.
  14.104 +
  14.105 +        o The 'handler' metadata is available via 'getStep'.
  14.106 +        """
  14.107 +        result = {}
  14.108 +
  14.109 +        info = self._registered.get( key )
  14.110 +
  14.111 +        if info is None:
  14.112 +            return default
  14.113 +
  14.114 +        return info.copy()
  14.115 +
  14.116 +    security.declareProtected( ManagePortal, 'listStepMetadata' )
  14.117 +    def listStepMetadata( self ):
  14.118 +
  14.119 +        """ Return a sequence of mappings describing registered steps.
  14.120 +
  14.121 +        o Mappings will be ordered alphabetically.
  14.122 +        """
  14.123 +        step_ids = self.listSteps()
  14.124 +        step_ids.sort()
  14.125 +        return [ self.getStepMetadata( x ) for x in step_ids ]
  14.126 +
  14.127 +    security.declareProtected( ManagePortal, 'generateXML' )
  14.128 +    def generateXML( self ):
  14.129 +
  14.130 +        """ Return a round-trippable XML representation of the registry.
  14.131 +
  14.132 +        o 'handler' values are serialized using their dotted names.
  14.133 +        """
  14.134 +        return self._exportTemplate()
  14.135 +
  14.136 +    security.declarePrivate( 'getStep' )
  14.137 +    def getStep( self, key, default=None ):
  14.138 +
  14.139 +        """ Return the IImportPlugin registered for 'key'.
  14.140 +
  14.141 +        o Return 'default' if no such step is registered.
  14.142 +        """
  14.143 +        marker = object()
  14.144 +        info = self._registered.get( key, marker )
  14.145 +
  14.146 +        if info is marker:
  14.147 +            return default
  14.148 +
  14.149 +        return _resolveDottedName( info[ 'handler' ] )
  14.150 +
  14.151 +    security.declarePrivate( 'registerStep' )
  14.152 +    def registerStep( self
  14.153 +                    , id
  14.154 +                    , version
  14.155 +                    , handler
  14.156 +                    , dependencies=()
  14.157 +                    , title=None
  14.158 +                    , description=None
  14.159 +                    ):
  14.160 +        """ Register a setup step.
  14.161 +
  14.162 +        o 'id' is a unique name for this step,
  14.163 +
  14.164 +        o 'version' is a string for comparing versions, it is preferred to
  14.165 +          be a yyyy/mm/dd-ii formatted string (date plus two-digit
  14.166 +          ordinal).  when comparing two version strings, the version with
  14.167 +          the lower sort order is considered the older version.
  14.168 +
  14.169 +          - Newer versions of a step supplant older ones.
  14.170 +
  14.171 +          - Attempting to register an older one after a newer one results
  14.172 +            in a KeyError.
  14.173 +
  14.174 +        o 'handler' should implement IImportPlugin.
  14.175 +
  14.176 +        o 'dependencies' is a tuple of step ids which have to run before
  14.177 +          this step in order to be able to run at all. Registration of
  14.178 +          steps that have unmet dependencies are deferred until the
  14.179 +          dependencies have been registered.
  14.180 +
  14.181 +        o 'title' is a one-line UI description for this step.
  14.182 +          If None, the first line of the documentation string of the handler
  14.183 +          is used, or the id if no docstring can be found.
  14.184 +
  14.185 +        o 'description' is a one-line UI description for this step.
  14.186 +          If None, the remaining line of the documentation string of
  14.187 +          the handler is used, or default to ''.
  14.188 +        """
  14.189 +        already = self.getStepMetadata( id )
  14.190 +
  14.191 +        if already and already[ 'version' ] > version:
  14.192 +            raise KeyError( 'Existing registration for step %s, version %s'
  14.193 +                          % ( id, already[ 'version' ] ) )
  14.194 +
  14.195 +        if title is None or description is None:
  14.196 +
  14.197 +            t, d = _extractDocstring( handler, id, '' )
  14.198 +
  14.199 +            title = title or t
  14.200 +            description = description or d
  14.201 +
  14.202 +        info = { 'id'           : id
  14.203 +               , 'version'      : version
  14.204 +               , 'handler'      : _getDottedName( handler )
  14.205 +               , 'dependencies' : dependencies
  14.206 +               , 'title'        : title
  14.207 +               , 'description'  : description
  14.208 +               }
  14.209 +
  14.210 +        self._registered[ id ] = info
  14.211 +
  14.212 +    security.declarePrivate( 'parseXML' )
  14.213 +    def parseXML( self, text, encoding=None ):
  14.214 +
  14.215 +        """ Parse 'text'.
  14.216 +        """
  14.217 +        reader = getattr( text, 'read', None )
  14.218 +
  14.219 +        if reader is not None:
  14.220 +            text = reader()
  14.221 +
  14.222 +        parser = _ImportStepRegistryParser( encoding )
  14.223 +        parseString( text, parser )
  14.224 +
  14.225 +        return parser._parsed
  14.226 +
  14.227 +    security.declarePrivate( 'clear' )
  14.228 +    def clear( self ):
  14.229 +
  14.230 +        self._registered = {}
  14.231 +
  14.232 +    #
  14.233 +    #   Helper methods
  14.234 +    #
  14.235 +    security.declarePrivate( '_computeTopologicalSort' )
  14.236 +    def _computeTopologicalSort( self ):
  14.237 +
  14.238 +        result = []
  14.239 +
  14.240 +        graph = [ ( x[ 'id' ], x[ 'dependencies' ] )
  14.241 +                    for x in self._registered.values() ]
  14.242 +
  14.243 +        for node, edges in graph:
  14.244 +
  14.245 +            after = -1
  14.246 +
  14.247 +            for edge in edges:
  14.248 +
  14.249 +                if edge in result:
  14.250 +                    after = max( after, result.index( edge ) )
  14.251 +
  14.252 +            result.insert( after + 1, node )
  14.253 +
  14.254 +        return result
  14.255 +
  14.256 +    security.declarePrivate( '_exportTemplate' )
  14.257 +    _exportTemplate = PageTemplateFile( 'isrExport.xml', _xmldir )
  14.258 +
  14.259 +InitializeClass( ImportStepRegistry )
  14.260 +
  14.261 +
  14.262 +class ExportStepRegistry( Implicit ):
  14.263 +
  14.264 +    """ Registry of known site-configuration export steps.
  14.265 +
  14.266 +    o Each step is registered with a unique id.
  14.267 +
  14.268 +    o When called, with the portal object passed in as an argument,
  14.269 +      the step must return a sequence of three-tuples,
  14.270 +      ( 'data', 'content_type', 'filename' ), one for each file exported
  14.271 +      by the step.
  14.272 +
  14.273 +      - 'data' is a string containing the file data;
  14.274 +
  14.275 +      - 'content_type' is the MIME type of the data;
  14.276 +
  14.277 +      - 'filename' is a suggested filename for use when downloading.
  14.278 +
  14.279 +    """
  14.280 +    __implements__ = ( IExportStepRegistry, )
  14.281 +
  14.282 +    security = ClassSecurityInfo()
  14.283 +
  14.284 +    def __init__( self ):
  14.285 +
  14.286 +        self.clear()
  14.287 +
  14.288 +    security.declareProtected( ManagePortal, 'listSteps' )
  14.289 +    def listSteps( self ):
  14.290 +
  14.291 +        """ Return a list of registered step IDs.
  14.292 +        """
  14.293 +        return self._registered.keys()
  14.294 +
  14.295 +    security.declareProtected( ManagePortal, 'getStepMetadata' )
  14.296 +    def getStepMetadata( self, key, default=None ):
  14.297 +
  14.298 +        """ Return a mapping of metadata for the step identified by 'key'.
  14.299 +
  14.300 +        o Return 'default' if no such step is registered.
  14.301 +
  14.302 +        o The 'handler' metadata is available via 'getStep'.
  14.303 +        """
  14.304 +        info = self._registered.get( key )
  14.305 +
  14.306 +        if info is None:
  14.307 +            return default
  14.308 +
  14.309 +        return info.copy()
  14.310 +
  14.311 +    security.declareProtected( ManagePortal, 'listStepMetadata' )
  14.312 +    def listStepMetadata( self ):
  14.313 +
  14.314 +        """ Return a sequence of mappings describing registered steps.
  14.315 +
  14.316 +        o Steps will be alphabetical by ID.
  14.317 +        """
  14.318 +        step_ids = self.listSteps()
  14.319 +        step_ids.sort()
  14.320 +        return [ self.getStepMetadata( x ) for x in step_ids ]
  14.321 +
  14.322 +    security.declareProtected( ManagePortal, 'generateXML' )
  14.323 +    def generateXML( self ):
  14.324 +
  14.325 +        """ Return a round-trippable XML representation of the registry.
  14.326 +
  14.327 +        o 'handler' values are serialized using their dotted names.
  14.328 +        """
  14.329 +        return self._exportTemplate()
  14.330 +
  14.331 +    security.declarePrivate( 'getStep' )
  14.332 +    def getStep( self, key, default=None ):
  14.333 +
  14.334 +        """ Return the IExportPlugin registered for 'key'.
  14.335 +
  14.336 +        o Return 'default' if no such step is registered.
  14.337 +        """
  14.338 +        marker = object()
  14.339 +        info = self._registered.get( key, marker )
  14.340 +
  14.341 +        if info is marker:
  14.342 +            return default
  14.343 +
  14.344 +        return _resolveDottedName( info[ 'handler' ] )
  14.345 +
  14.346 +    security.declarePrivate( 'registerStep' )
  14.347 +    def registerStep( self, id, handler, title=None, description=None ):
  14.348 +
  14.349 +        """ Register an export step.
  14.350 +
  14.351 +        o 'id' is the unique identifier for this step
  14.352 +
  14.353 +        o 'step' should implement IExportPlugin.
  14.354 +
  14.355 +        o 'title' is a one-line UI description for this step.
  14.356 +          If None, the first line of the documentation string of the step
  14.357 +          is used, or the id if no docstring can be found.
  14.358 +
  14.359 +        o 'description' is a one-line UI description for this step.
  14.360 +          If None, the remaining line of the documentation string of
  14.361 +          the step is used, or default to ''.
  14.362 +        """
  14.363 +        if title is None or description is None:
  14.364 +
  14.365 +            t, d = _extractDocstring( handler, id, '' )
  14.366 +
  14.367 +            title = title or t
  14.368 +            description = description or d
  14.369 +
  14.370 +        info = { 'id'           : id
  14.371 +               , 'handler'      : _getDottedName( handler )
  14.372 +               , 'title'        : title
  14.373 +               , 'description'  : description
  14.374 +               }
  14.375 +
  14.376 +        self._registered[ id ] = info
  14.377 +
  14.378 +    security.declarePrivate( 'parseXML' )
  14.379 +    def parseXML( self, text, encoding=None ):
  14.380 +
  14.381 +        """ Parse 'text'.
  14.382 +        """
  14.383 +        reader = getattr( text, 'read', None )
  14.384 +
  14.385 +        if reader is not None:
  14.386 +            text = reader()
  14.387 +
  14.388 +        parser = _ExportStepRegistryParser( encoding )
  14.389 +        parseString( text, parser )
  14.390 +
  14.391 +        return parser._parsed
  14.392 +
  14.393 +    security.declarePrivate( 'clear' )
  14.394 +    def clear( self ):
  14.395 +
  14.396 +        self._registered = {}
  14.397 +
  14.398 +    #
  14.399 +    #   Helper methods
  14.400 +    #
  14.401 +    security.declarePrivate( '_exportTemplate' )
  14.402 +    _exportTemplate = PageTemplateFile( 'esrExport.xml', _xmldir )
  14.403 +
  14.404 +InitializeClass( ExportStepRegistry )
  14.405 +
  14.406 +class ToolsetRegistry( Implicit ):
  14.407 +
  14.408 +    """ Track required / forbidden tools.
  14.409 +    """
  14.410 +    __implements__ = ( IToolsetRegistry, )
  14.411 +
  14.412 +    security = ClassSecurityInfo()
  14.413 +    security.setDefaultAccess( 'allow' )
  14.414 +
  14.415 +    def __init__( self ):
  14.416 +
  14.417 +        self.clear()
  14.418 +
  14.419 +    #
  14.420 +    #   Toolset API
  14.421 +    #
  14.422 +    security.declareProtected( ManagePortal, 'listForbiddenTools' )
  14.423 +    def listForbiddenTools( self ):
  14.424 +
  14.425 +        """ See IToolsetRegistry.
  14.426 +        """
  14.427 +        result = list( self._forbidden )
  14.428 +        result.sort()
  14.429 +        return result
  14.430 +
  14.431 +    security.declareProtected( ManagePortal, 'addForbiddenTool' )
  14.432 +    def addForbiddenTool( self, tool_id ):
  14.433 +
  14.434 +        """ See IToolsetRegistry.
  14.435 +        """
  14.436 +        if tool_id in self._forbidden:
  14.437 +            return
  14.438 +
  14.439 +        if self._required.get( tool_id ) is not None:
  14.440 +            raise ValueError, 'Tool %s is required!' % tool_id
  14.441 +
  14.442 +        self._forbidden.append( tool_id )
  14.443 +
  14.444 +    security.declareProtected( ManagePortal, 'listRequiredTools' )
  14.445 +    def listRequiredTools( self ):
  14.446 +
  14.447 +        """ See IToolsetRegistry.
  14.448 +        """
  14.449 +        result = list( self._required.keys() )
  14.450 +        result.sort()
  14.451 +        return result
  14.452 +
  14.453 +    security.declareProtected( ManagePortal, 'getRequiredToolInfo' )
  14.454 +    def getRequiredToolInfo( self, tool_id ):
  14.455 +
  14.456 +        """ See IToolsetRegistry.
  14.457 +        """
  14.458 +        return self._required[ tool_id ]
  14.459 +
  14.460 +    security.declareProtected( ManagePortal, 'listRequiredToolInfo' )
  14.461 +    def listRequiredToolInfo( self ):
  14.462 +
  14.463 +        """ See IToolsetRegistry.
  14.464 +        """
  14.465 +        return [ self.getRequiredToolInfo( x )
  14.466 +                        for x in self.listRequiredTools() ]
  14.467 +
  14.468 +    security.declareProtected( ManagePortal, 'addRequiredTool' )
  14.469 +    def addRequiredTool( self, tool_id, dotted_name ):
  14.470 +
  14.471 +        """ See IToolsetRegistry.
  14.472 +        """
  14.473 +        if tool_id in self._forbidden:
  14.474 +            raise ValueError, "Forbidden tool ID: %s" % tool_id
  14.475 +
  14.476 +        self._required[ tool_id ] = { 'id' : tool_id
  14.477 +                                    , 'class' : dotted_name
  14.478 +                                    }
  14.479 +
  14.480 +    security.declareProtected( ManagePortal, 'generateXML' )
  14.481 +    def generateXML( self ):
  14.482 +
  14.483 +        """ Pseudo API.
  14.484 +        """
  14.485 +        return self._toolsetConfig()
  14.486 +
  14.487 +    security.declareProtected( ManagePortal, 'parseXML' )
  14.488 +    def parseXML( self, text, encoding=None ):
  14.489 +
  14.490 +        """ Pseudo-API
  14.491 +        """
  14.492 +        reader = getattr( text, 'read', None )
  14.493 +
  14.494 +        if reader is not None:
  14.495 +            text = reader()
  14.496 +
  14.497 +        parser = _ToolsetParser( encoding )
  14.498 +        parseString( text, parser )
  14.499 +
  14.500 +        for tool_id in parser._forbidden:
  14.501 +            self.addForbiddenTool( tool_id )
  14.502 +
  14.503 +        for tool_id, dotted_name in parser._required.items():
  14.504 +            self.addRequiredTool( tool_id, dotted_name )
  14.505 +
  14.506 +    security.declarePrivate( 'clear' )
  14.507 +    def clear( self ):
  14.508 +
  14.509 +        self._forbidden = []
  14.510 +        self._required = {}
  14.511 +
  14.512 +    #
  14.513 +    #   Helper methods.
  14.514 +    #
  14.515 +    security.declarePrivate( '_toolsetConfig' )
  14.516 +    _toolsetConfig = PageTemplateFile( 'tscExport.xml'
  14.517 +                                     , _xmldir
  14.518 +                                     , __name__='toolsetConfig'
  14.519 +                                     )
  14.520 +
  14.521 +InitializeClass( ToolsetRegistry )
  14.522 +
  14.523 +class ProfileRegistry( Implicit ):
  14.524 +
  14.525 +    """ Track registered profiles.
  14.526 +    """
  14.527 +    __implements__ = ( IProfileRegistry, )
  14.528 +
  14.529 +    security = ClassSecurityInfo()
  14.530 +    security.setDefaultAccess( 'allow' )
  14.531 +
  14.532 +    def __init__( self ):
  14.533 +
  14.534 +        self.clear()
  14.535 +
  14.536 +    security.declareProtected( ManagePortal, '' )
  14.537 +    def getProfileInfo( self, profile_id ):
  14.538 +
  14.539 +        """ See IProfileRegistry.
  14.540 +        """
  14.541 +        result = self._profile_info[ profile_id ]
  14.542 +        return result.copy()
  14.543 +
  14.544 +    security.declareProtected( ManagePortal, 'listProfiles' )
  14.545 +    def listProfiles( self ):
  14.546 +
  14.547 +        """ See IProfileRegistry.
  14.548 +        """
  14.549 +        return tuple( self._profile_ids )
  14.550 +
  14.551 +    security.declareProtected( ManagePortal, 'listProfileInfo' )
  14.552 +    def listProfileInfo( self ):
  14.553 +
  14.554 +        """ See IProfileRegistry.
  14.555 +        """
  14.556 +        return [ self.getProfileInfo( id ) for id in self.listProfiles() ]
  14.557 +
  14.558 +    security.declareProtected( ManagePortal, 'registerProfile' )
  14.559 +    def registerProfile( self
  14.560 +                       , name
  14.561 +                       , title
  14.562 +                       , description
  14.563 +                       , path
  14.564 +                       , product=None
  14.565 +                       , profile_type=BASE
  14.566 +                       ):
  14.567 +        """ See IProfileRegistry.
  14.568 +        """
  14.569 +        profile_id = '%s:%s' % (product or 'other', name)
  14.570 +        if self._profile_info.get( profile_id ) is not None:
  14.571 +            raise KeyError, 'Duplicate profile ID: %s' % profile_id
  14.572 +
  14.573 +        self._profile_ids.append( profile_id )
  14.574 +
  14.575 +        info = { 'id' : profile_id
  14.576 +               , 'title' : title
  14.577 +               , 'description' : description
  14.578 +               , 'path' : path
  14.579 +               , 'product' : product
  14.580 +               , 'type': profile_type
  14.581 +               }
  14.582 +
  14.583 +        self._profile_info[ profile_id ] = info
  14.584 +
  14.585 +    security.declarePrivate( 'clear' )
  14.586 +    def clear( self ):
  14.587 +
  14.588 +        self._profile_info = {}
  14.589 +        self._profile_ids = []
  14.590 +
  14.591 +InitializeClass( ProfileRegistry )
  14.592 +
  14.593 +_profile_registry = ProfileRegistry()
  14.594 +
  14.595 +class _ImportStepRegistryParser( HandlerBase ):
  14.596 +
  14.597 +    security = ClassSecurityInfo()
  14.598 +    security.declareObjectPrivate()
  14.599 +    security.setDefaultAccess( 'deny' )
  14.600 +
  14.601 +    def __init__( self, encoding ):
  14.602 +
  14.603 +        self._encoding = encoding
  14.604 +        self._started = False
  14.605 +        self._pending = None
  14.606 +        self._parsed = []
  14.607 +
  14.608 +    def startElement( self, name, attrs ):
  14.609 +
  14.610 +        if name == 'import-steps':
  14.611 +
  14.612 +            if self._started:
  14.613 +                raise ValueError, 'Duplicated setup-steps element: %s' % name
  14.614 +
  14.615 +            self._started = True
  14.616 +
  14.617 +        elif name == 'import-step':
  14.618 +
  14.619 +            if self._pending is not None:
  14.620 +                raise ValueError, 'Cannot nest setup-step elements'
  14.621 +
  14.622 +            self._pending = dict( [ ( k, self._extract( attrs, k ) )
  14.623 +                                    for k in attrs.keys() ] )
  14.624 +
  14.625 +            self._pending[ 'dependencies' ] = []
  14.626 +
  14.627 +        elif name == 'dependency':
  14.628 +
  14.629 +            if not self._pending:
  14.630 +                raise ValueError, 'Dependency outside of step'
  14.631 +
  14.632 +            depended = self._extract( attrs, 'step' )
  14.633 +            self._pending[ 'dependencies' ].append( depended )
  14.634 +
  14.635 +        else:
  14.636 +            raise ValueError, 'Unknown element %s' % name
  14.637 +
  14.638 +    def characters( self, content ):
  14.639 +
  14.640 +        if self._pending is not None:
  14.641 +            content = self._encode( content )
  14.642 +            self._pending.setdefault( 'description', [] ).append( content )
  14.643 +
  14.644 +    def endElement(self, name):
  14.645 +
  14.646 +        if name == 'import-steps':
  14.647 +            pass
  14.648 +
  14.649 +        elif name == 'import-step':
  14.650 +
  14.651 +            if self._pending is None:
  14.652 +                raise ValueError, 'No pending step!'
  14.653 +
  14.654 +            deps = tuple( self._pending[ 'dependencies' ] )
  14.655 +            self._pending[ 'dependencies' ] = deps
  14.656 +
  14.657 +            desc = ''.join( self._pending[ 'description' ] )
  14.658 +            self._pending[ 'description' ] = desc
  14.659 +
  14.660 +            self._parsed.append( self._pending )
  14.661 +            self._pending = None
  14.662 +
  14.663 +InitializeClass( _ImportStepRegistryParser )
  14.664 +
  14.665 +class _ExportStepRegistryParser( HandlerBase ):
  14.666 +
  14.667 +    security = ClassSecurityInfo()
  14.668 +    security.declareObjectPrivate()
  14.669 +    security.setDefaultAccess( 'deny' )
  14.670 +
  14.671 +    def __init__( self, encoding ):
  14.672 +
  14.673 +        self._encoding = encoding
  14.674 +        self._started = False
  14.675 +        self._pending = None
  14.676 +        self._parsed = []
  14.677 +
  14.678 +    def startElement( self, name, attrs ):
  14.679 +
  14.680 +        if name == 'export-steps':
  14.681 +
  14.682 +            if self._started:
  14.683 +                raise ValueError, 'Duplicated export-steps element: %s' % name
  14.684 +
  14.685 +            self._started = True
  14.686 +
  14.687 +        elif name == 'export-step':
  14.688 +
  14.689 +            if self._pending is not None:
  14.690 +                raise ValueError, 'Cannot nest export-step elements'
  14.691 +
  14.692 +            self._pending = dict( [ ( k, self._extract( attrs, k ) )
  14.693 +                                    for k in attrs.keys() ] )
  14.694 +
  14.695 +        else:
  14.696 +            raise ValueError, 'Unknown element %s' % name
  14.697 +
  14.698 +    def characters( self, content ):
  14.699 +
  14.700 +        if self._pending is not None:
  14.701 +            content = self._encode( content )
  14.702 +            self._pending.setdefault( 'description', [] ).append( content )
  14.703 +
  14.704 +    def endElement(self, name):
  14.705 +
  14.706 +        if name == 'export-steps':
  14.707 +            pass
  14.708 +
  14.709 +        elif name == 'export-step':
  14.710 +
  14.711 +            if self._pending is None:
  14.712 +                raise ValueError, 'No pending step!'
  14.713 +
  14.714 +            desc = ''.join( self._pending[ 'description' ] )
  14.715 +            self._pending[ 'description' ] = desc
  14.716 +
  14.717 +            self._parsed.append( self._pending )
  14.718 +            self._pending = None
  14.719 +
  14.720 +InitializeClass( _ExportStepRegistryParser )
  14.721 +
  14.722 +
  14.723 +class _ToolsetParser( HandlerBase ):
  14.724 +
  14.725 +    security = ClassSecurityInfo()
  14.726 +    security.declareObjectPrivate()
  14.727 +    security.setDefaultAccess( 'deny' )
  14.728 +
  14.729 +    def __init__( self, encoding ):
  14.730 +
  14.731 +        self._encoding = encoding
  14.732 +        self._required = {}
  14.733 +        self._forbidden = []
  14.734 +
  14.735 +    def startElement( self, name, attrs ):
  14.736 +
  14.737 +        if name == 'tool-setup':
  14.738 +            pass
  14.739 +
  14.740 +        elif name == 'forbidden':
  14.741 +
  14.742 +            tool_id = self._extract( attrs, 'tool_id' )
  14.743 +
  14.744 +            if tool_id not in self._forbidden:
  14.745 +                self._forbidden.append( tool_id )
  14.746 +
  14.747 +        elif name == 'required':
  14.748 +
  14.749 +            tool_id = self._extract( attrs, 'tool_id' )
  14.750 +            dotted_name = self._extract( attrs, 'class' )
  14.751 +            self._required[ tool_id ] = dotted_name
  14.752 +
  14.753 +        else:
  14.754 +            raise ValueError, 'Unknown element %s' % name
  14.755 +
  14.756 +
  14.757 +InitializeClass( _ToolsetParser )
    15.1 new file mode 100644
    15.2 --- /dev/null
    15.3 +++ b/rolemap.py
    15.4 @@ -0,0 +1,212 @@
    15.5 +##############################################################################
    15.6 +#
    15.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    15.8 +#
    15.9 +# This software is subject to the provisions of the Zope Public License,
   15.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   15.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   15.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   15.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   15.14 +# FOR A PARTICULAR PURPOSE.
   15.15 +#
   15.16 +##############################################################################
   15.17 +""" CMFSetup:  Role-permission export / import
   15.18 +
   15.19 +$Id: rolemap.py 36703 2004-12-14 20:56:26Z yuppie $
   15.20 +"""
   15.21 +
   15.22 +from AccessControl import ClassSecurityInfo
   15.23 +from AccessControl.Permission import Permission
   15.24 +from Globals import InitializeClass
   15.25 +from Products.PageTemplates.PageTemplateFile import PageTemplateFile
   15.26 +
   15.27 +from permissions import ManagePortal
   15.28 +from utils import _xmldir
   15.29 +from utils import ConfiguratorBase
   15.30 +from utils import CONVERTER, DEFAULT, KEY
   15.31 +
   15.32 +
   15.33 +#
   15.34 +#   Configurator entry points
   15.35 +#
   15.36 +_FILENAME = 'rolemap.xml'
   15.37 +
   15.38 +def importRolemap( context ):
   15.39 +
   15.40 +    """ Import roles / permission map from an XML file.
   15.41 +
   15.42 +    o 'context' must implement IImportContext.
   15.43 +
   15.44 +    o Register via Python:
   15.45 +
   15.46 +      registry = site.portal_setup.setup_steps
   15.47 +      registry.registerStep( 'importRolemap'
   15.48 +                           , '20040518-01'
   15.49 +                           , Products.CMFSetup.rolemap.importRolemap
   15.50 +                           , ()
   15.51 +                           , 'Role / Permission import'
   15.52 +                           , 'Import additional roles, and map '
   15.53 +                           'roles to permissions'
   15.54 +                           )
   15.55 +
   15.56 +    o Register via XML:
   15.57 +
   15.58 +      <setup-step id="importRolemap"
   15.59 +                  version="20040518-01"
   15.60 +                  handler="Products.CMFSetup.rolemap.importRolemap"
   15.61 +                  title="Role / Permission import"
   15.62 +      >Import additional roles, and map roles to permissions.</setup-step>
   15.63 +
   15.64 +    """
   15.65 +    site = context.getSite()
   15.66 +    encoding = context.getEncoding()
   15.67 +
   15.68 +    if context.shouldPurge():
   15.69 +
   15.70 +        items = site.__dict__.items()
   15.71 +
   15.72 +        for k, v in items: # XXX: WAAA
   15.73 +
   15.74 +            if k == '__ac_roles__':
   15.75 +                delattr( site, k )
   15.76 +
   15.77 +            if k.startswith( '_' ) and k.endswith( '_Permission' ):
   15.78 +                delattr( site, k )
   15.79 +
   15.80 +    text = context.readDataFile( _FILENAME )
   15.81 +
   15.82 +    if text is not None:
   15.83 +
   15.84 +        rc = RolemapConfigurator( site, encoding )
   15.85 +        rolemap_info = rc.parseXML( text )
   15.86 +
   15.87 +        immediate_roles = list( getattr( site, '__ac_roles__', [] ) )[:]
   15.88 +        already = {}
   15.89 +
   15.90 +        for role in site.valid_roles():
   15.91 +            already[ role ] = 1
   15.92 +
   15.93 +        for role in rolemap_info[ 'roles' ]:
   15.94 +
   15.95 +            if already.get( role ) is None:
   15.96 +                immediate_roles.append( role )
   15.97 +                already[ role ] = 1
   15.98 +
   15.99 +        immediate_roles.sort()
  15.100 +        site.__ac_roles__ = tuple( immediate_roles )
  15.101 +
  15.102 +        for permission in rolemap_info[ 'permissions' ]:
  15.103 +
  15.104 +            site.manage_permission( permission[ 'name' ]
  15.105 +                                  , permission[ 'roles' ]
  15.106 +                                  , permission[ 'acquire' ]
  15.107 +                                  )
  15.108 +
  15.109 +    return 'Role / permission map imported.'
  15.110 +
  15.111 +
  15.112 +def exportRolemap( context ):
  15.113 +
  15.114 +    """ Export roles / permission map as an XML file
  15.115 +
  15.116 +    o 'context' must implement IExportContext.
  15.117 +
  15.118 +    o Register via Python:
  15.119 +
  15.120 +      registry = site.portal_setup.export_steps
  15.121 +      registry.registerStep( 'exportRolemap'
  15.122 +                           , Products.CMFSetup.rolemap.exportRolemap
  15.123 +                           , 'Role / Permission export'
  15.124 +                           , 'Export additional roles, and '
  15.125 +                             'role / permission map '
  15.126 +                           )
  15.127 +
  15.128 +    o Register via XML:
  15.129 +
  15.130 +      <export-script id="exportRolemap"
  15.131 +                     version="20040518-01"
  15.132 +                     handler="Products.CMFSetup.rolemap.exportRolemap"
  15.133 +                     title="Role / Permission export"
  15.134 +      >Export additional roles, and role / permission map.</export-script>
  15.135 +
  15.136 +    """
  15.137 +    site = context.getSite()
  15.138 +    rc = RolemapConfigurator( site ).__of__( site )
  15.139 +    text = rc.generateXML()
  15.140 +
  15.141 +    context.writeDataFile( _FILENAME, text, 'text/xml' )
  15.142 +
  15.143 +    return 'Role / permission map exported.'
  15.144 +
  15.145 +
  15.146 +class RolemapConfigurator(ConfiguratorBase):
  15.147 +    """ Synthesize XML description of sitewide role-permission settings.
  15.148 +    """
  15.149 +    security = ClassSecurityInfo()
  15.150 +
  15.151 +    security.declareProtected( ManagePortal, 'listRoles' )
  15.152 +    def listRoles( self ):
  15.153 +
  15.154 +        """ List the valid role IDs for our site.
  15.155 +        """
  15.156 +        return self._site.valid_roles()
  15.157 +
  15.158 +    security.declareProtected( ManagePortal, 'listPermissions' )
  15.159 +    def listPermissions( self ):
  15.160 +
  15.161 +        """ List permissions for export.
  15.162 +
  15.163 +        o Returns a sqeuence of mappings describing locally-modified
  15.164 +          permission / role settings.  Keys include:
  15.165 +
  15.166 +          'permission' -- the name of the permission
  15.167 +
  15.168 +          'acquire' -- a flag indicating whether to acquire roles from the
  15.169 +              site's container
  15.170 +
  15.171 +          'roles' -- the list of roles which have the permission.
  15.172 +
  15.173 +        o Do not include permissions which both acquire and which define
  15.174 +          no local changes to the acquired policy.
  15.175 +        """
  15.176 +        permissions = []
  15.177 +        valid_roles = self.listRoles()
  15.178 +
  15.179 +        for perm in self._site.ac_inherited_permissions( 1 ):
  15.180 +
  15.181 +            name = perm[ 0 ]
  15.182 +            p = Permission( name, perm[ 1 ], self._site )
  15.183 +            roles = p.getRoles( default=[] )
  15.184 +            acquire = isinstance( roles, list )  # tuple means don't acquire
  15.185 +            roles = [ r for r in roles if r in valid_roles ]
  15.186 +
  15.187 +            if roles or not acquire:
  15.188 +                permissions.append( { 'name'    : name
  15.189 +                                    , 'acquire' : acquire
  15.190 +                                    , 'roles'   : roles
  15.191 +                                    } )
  15.192 +
  15.193 +        return permissions
  15.194 +
  15.195 +    def _getExportTemplate(self):
  15.196 +
  15.197 +        return PageTemplateFile('rmeExport.xml', _xmldir)
  15.198 +
  15.199 +    def _getImportMapping(self):
  15.200 +
  15.201 +        return {
  15.202 +          'rolemap':
  15.203 +            { 'roles':       {CONVERTER: self._convertToUnique},
  15.204 +              'permissions': {CONVERTER: self._convertToUnique} },
  15.205 +          'roles':
  15.206 +            { 'role':        {KEY: None} },
  15.207 +          'role':
  15.208 +            { 'name':        {KEY: None} },
  15.209 +          'permissions':
  15.210 +            { 'permission':  {KEY: None, DEFAULT: ()} },
  15.211 +          'permission':
  15.212 +            { 'name':        {},
  15.213 +              'role':        {KEY: 'roles'},
  15.214 +              'acquire':     {CONVERTER: self._convertToBoolean} } }
  15.215 +
  15.216 +InitializeClass(RolemapConfigurator)
    16.1 new file mode 100644
    16.2 --- /dev/null
    16.3 +++ b/skins.py
    16.4 @@ -0,0 +1,290 @@
    16.5 +##############################################################################
    16.6 +#
    16.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    16.8 +#
    16.9 +# This software is subject to the provisions of the Zope Public License,
   16.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   16.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   16.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   16.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   16.14 +# FOR A PARTICULAR PURPOSE.
   16.15 +#
   16.16 +##############################################################################
   16.17 +""" Skin path configuration management
   16.18 +
   16.19 +Setup step and export script
   16.20 +
   16.21 +$Id: skins.py 37010 2005-05-07 12:44:18Z jens $
   16.22 +"""
   16.23 +
   16.24 +import re
   16.25 +
   16.26 +from AccessControl import ClassSecurityInfo
   16.27 +from Globals import InitializeClass
   16.28 +from Products.PageTemplates.PageTemplateFile import PageTemplateFile
   16.29 +
   16.30 +from Products.CMFCore.utils import getToolByName
   16.31 +from Products.CMFCore.utils import minimalpath
   16.32 +from Products.CMFCore.DirectoryView import createDirectoryView
   16.33 +from Products.CMFCore.DirectoryView import DirectoryView
   16.34 +
   16.35 +from permissions import ManagePortal
   16.36 +from utils import _xmldir
   16.37 +from utils import ConfiguratorBase
   16.38 +from utils import CONVERTER, DEFAULT, KEY
   16.39 +
   16.40 +
   16.41 +#
   16.42 +#   Entry points
   16.43 +#
   16.44 +_FILENAME = 'skins.xml'
   16.45 +
   16.46 +def importSkinsTool( context ):
   16.47 +
   16.48 +    """ Import skins tool FSDirViews and skin paths from an XML file
   16.49 +
   16.50 +    o 'context' must implement IImportContext.
   16.51 +
   16.52 +    o Register via Python:
   16.53 +
   16.54 +      registry = site.portal_setup.getImportStepRegistry()
   16.55 +      registry.registerStep( 'importSkinsTool'
   16.56 +                           , '20040518-01'
   16.57 +                           , Products.CMFSetup.skins.importSkinsTool
   16.58 +                           , ()
   16.59 +                           , 'Skins Tool import'
   16.60 +                           , 'Import skins tool FSDVs and skin paths.'
   16.61 +                           )
   16.62 +
   16.63 +    o Register via XML:
   16.64 +
   16.65 +      <setup-step id="importSkinsTool"
   16.66 +                  version="20040524-01"
   16.67 +                  handler="Products.CMFSetup.skins.importSkinsTool"
   16.68 +                  title="Skins Tool import"
   16.69 +      >Import skins tool FSDVs and skin paths.</setup-step>
   16.70 +
   16.71 +    """
   16.72 +    site = context.getSite()
   16.73 +    encoding = context.getEncoding()
   16.74 +
   16.75 +    stool = getToolByName(site, 'portal_skins')
   16.76 +
   16.77 +    if context.shouldPurge():
   16.78 +
   16.79 +        stool.request_varname = 'portal_skin'
   16.80 +        stool.allow_any = 0
   16.81 +        stool.cookie_persistence = 0
   16.82 +
   16.83 +        stool._getSelections().clear()
   16.84 +
   16.85 +        for id in stool.objectIds(DirectoryView.meta_type):
   16.86 +            stool._delObject(id)
   16.87 +
   16.88 +    text = context.readDataFile( _FILENAME )
   16.89 +
   16.90 +    if text is not None:
   16.91 +
   16.92 +        stc = SkinsToolConfigurator( site, encoding )
   16.93 +        tool_info = stc.parseXML( text )
   16.94 +
   16.95 +        if 'default_skin' in tool_info:
   16.96 +            stool.default_skin = str(tool_info['default_skin'])
   16.97 +        if 'request_varname' in tool_info:
   16.98 +            stool.request_varname = str(tool_info['request_varname'])
   16.99 +        if 'allow_any' in tool_info:
  16.100 +            stool.allow_any = tool_info['allow_any'] and 1 or 0
  16.101 +        if 'cookie_persistence' in tool_info:
  16.102 +            stool.cookie_persistence = \
  16.103 +                                    tool_info['cookie_persistence'] and 1 or 0
  16.104 +
  16.105 +        for dir_info in tool_info['skin_dirs']:
  16.106 +            dir_id = dir_info['id']
  16.107 +            if dir_id in stool.objectIds(DirectoryView.meta_type):
  16.108 +                stool._delObject(dir_id)
  16.109 +            createDirectoryView(stool, dir_info['directory'], dir_id)
  16.110 +
  16.111 +        for path_info in tool_info['skin_paths']:
  16.112 +            path_id = path_info['id']
  16.113 +            if path_id == '*':
  16.114 +                for path_id, path in stool._getSelections().items():
  16.115 +                    path = _updatePath(path, path_info['layers'])
  16.116 +                    stool.addSkinSelection(path_id, path)
  16.117 +            else:
  16.118 +                if stool._getSelections().has_key(path_id):
  16.119 +                    path = stool._getSelections()[path_id]
  16.120 +                else:
  16.121 +                    path = ''
  16.122 +                path = _updatePath(path, path_info['layers'])
  16.123 +                stool.addSkinSelection(path_id, path)
  16.124 +
  16.125 +    #
  16.126 +    #   Purge and rebuild the skin path, now that we have added our stuff.
  16.127 +    #   Don't bother if no REQUEST is present, e.g. when running unit tests
  16.128 +    #
  16.129 +    request = getattr(site, 'REQUEST', None)
  16.130 +    if request is not None:
  16.131 +        site.clearCurrentSkin()
  16.132 +        site.setupCurrentSkin(request)
  16.133 +
  16.134 +    return 'Skins tool imported'
  16.135 +
  16.136 +def _updatePath(path, layer_infos):
  16.137 +    path = [ name.strip() for name in path.split(',') if name.strip() ]
  16.138 +
  16.139 +    for layer in layer_infos:
  16.140 +        if layer['name'] in path:
  16.141 +            path.remove(layer['name'])
  16.142 +
  16.143 +        if 'insert-before' in layer:
  16.144 +            if layer['insert-before'] == '*':
  16.145 +                path.insert(0, layer['name'])
  16.146 +                continue
  16.147 +            else:
  16.148 +                try:
  16.149 +                    index = path.index(layer['insert-before'])
  16.150 +                    path.insert(index, layer['name'])
  16.151 +                    continue
  16.152 +                except ValueError:
  16.153 +                    pass
  16.154 +        elif 'insert-after' in layer:
  16.155 +            if layer['insert-after'] == '*':
  16.156 +                pass
  16.157 +            else:
  16.158 +                try:
  16.159 +                    index = path.index(layer['insert-after'])
  16.160 +                    path.insert(index+1, layer['name'])
  16.161 +                    continue
  16.162 +                except ValueError:
  16.163 +                    pass
  16.164 +
  16.165 +        if not 'remove' in layer:
  16.166 +            path.append(layer['name'])
  16.167 +
  16.168 +    return str( ','.join(path) )
  16.169 +
  16.170 +def exportSkinsTool( context ):
  16.171 +
  16.172 +    """ Export skins tool FSDVs and skin paths as an XML file
  16.173 +
  16.174 +    o 'context' must implement IExportContext.
  16.175 +
  16.176 +    o Register via Python:
  16.177 +
  16.178 +      registry = site.portal_setup.getExportStepRegistry()
  16.179 +      registry.registerStep( 'exportSkinsTool'
  16.180 +                           , Products.CMFSetup.skins.exportSkinsTool
  16.181 +                           , 'Skins Tool export'
  16.182 +                           , 'Export skins tool FSDVs and skin paths.'
  16.183 +                           )
  16.184 +
  16.185 +    o Register via XML:
  16.186 +
  16.187 +      <export-script id="exportSkinsTool"
  16.188 +                     version="20040518-01"
  16.189 +                     handler="Products.CMFSetup.skins.exportSkinsTool"
  16.190 +                     title="Skins Tool export"
  16.191 +      >Export skins tool FSDVs and skin paths.</export-script>
  16.192 +
  16.193 +    """
  16.194 +    site = context.getSite()
  16.195 +    stc = SkinsToolConfigurator( site ).__of__( site )
  16.196 +    text = stc.generateXML()
  16.197 +
  16.198 +    context.writeDataFile( _FILENAME, text, 'text/xml' )
  16.199 +
  16.200 +    return 'Skins tool exported.'
  16.201 +
  16.202 +
  16.203 +class SkinsToolConfigurator(ConfiguratorBase):
  16.204 +
  16.205 +    security = ClassSecurityInfo()
  16.206 +
  16.207 +    _COMMA_SPLITTER = re.compile( r',[ ]*' )
  16.208 +
  16.209 +    security.declareProtected(ManagePortal, 'getToolInfo' )
  16.210 +    def getToolInfo( self ):
  16.211 +
  16.212 +        """ Return the tool's settings.
  16.213 +        """
  16.214 +        stool = getToolByName(self._site, 'portal_skins')
  16.215 +
  16.216 +        return { 'default_skin': stool.default_skin,
  16.217 +                 'request_varname': stool.request_varname,
  16.218 +                 'allow_any': stool.allow_any,
  16.219 +                 'cookie_persistence': stool.cookie_persistence }
  16.220 +
  16.221 +    security.declareProtected(ManagePortal, 'listSkinPaths' )
  16.222 +    def listSkinPaths( self ):
  16.223 +
  16.224 +        """ Return a sequence of mappings for each skin path in the tool.
  16.225 +
  16.226 +        o Keys include:
  16.227 +
  16.228 +          'id' -- folder ID
  16.229 +
  16.230 +          'path' -- sequence of layer IDs
  16.231 +        """
  16.232 +        stool = getToolByName(self._site, 'portal_skins')
  16.233 +
  16.234 +        return [ { 'id' : k
  16.235 +                 , 'path' : self._COMMA_SPLITTER.split( v )
  16.236 +                 } for k, v in stool.getSkinPaths() ]
  16.237 +
  16.238 +    security.declareProtected(ManagePortal, 'listFSDirectoryViews' )
  16.239 +    def listFSDirectoryViews( self ):
  16.240 +
  16.241 +        """ Return a sequence of mappings for each FSDV.
  16.242 +
  16.243 +        o Keys include:
  16.244 +
  16.245 +          'id' -- FSDV ID
  16.246 +
  16.247 +          'directory' -- filesystem path of the FSDV.
  16.248 +        """
  16.249 +        result = []
  16.250 +        stool = getToolByName(self._site, 'portal_skins')
  16.251 +
  16.252 +        fsdvs = stool.objectItems( DirectoryView.meta_type )
  16.253 +        fsdvs.sort()
  16.254 +
  16.255 +        for id, fsdv in fsdvs:
  16.256 +
  16.257 +            dirpath = fsdv._dirpath
  16.258 +
  16.259 +            if dirpath.startswith( '/' ):
  16.260 +                dirpath = minimalpath( fsdv._dirpath )
  16.261 +
  16.262 +            result.append( { 'id' : id
  16.263 +                           , 'directory' : dirpath
  16.264 +                           } )
  16.265 +
  16.266 +        return result
  16.267 +
  16.268 +    def _getExportTemplate(self):
  16.269 +
  16.270 +        return PageTemplateFile('stcExport.xml', _xmldir)
  16.271 +
  16.272 +    def _getImportMapping(self):
  16.273 +
  16.274 +        return {
  16.275 +          'skins-tool':
  16.276 +            { 'default_skin':       {},
  16.277 +              'request_varname':    {},
  16.278 +              'allow_any':          {CONVERTER: self._convertToBoolean},
  16.279 +              'cookie_persistence': {CONVERTER: self._convertToBoolean},
  16.280 +              'skin-directory':     {KEY: 'skin_dirs', DEFAULT: ()},
  16.281 +              'skin-path':          {KEY: 'skin_paths', DEFAULT: ()} },
  16.282 +          'skin-directory':
  16.283 +            { 'id':                 {},
  16.284 +              'directory':          {} },
  16.285 +          'skin-path':
  16.286 +            { 'id':                 {},
  16.287 +              'layer':              {KEY: 'layers', DEFAULT: ()} },
  16.288 +          'layer':
  16.289 +            { 'name':               {},
  16.290 +              'insert-after':       {},
  16.291 +              'insert-before':      {},
  16.292 +              'remove':             {} } }
  16.293 +
  16.294 +InitializeClass(SkinsToolConfigurator)
    17.1 new file mode 100644
    17.2 --- /dev/null
    17.3 +++ b/tests/__init__.py
    17.4 @@ -0,0 +1,16 @@
    17.5 +##############################################################################
    17.6 +#
    17.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    17.8 +#
    17.9 +# This software is subject to the provisions of the Zope Public License,
   17.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   17.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   17.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   17.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   17.14 +# FOR A PARTICULAR PURPOSE.
   17.15 +#
   17.16 +##############################################################################
   17.17 +""" CMFSetup product unit tests.
   17.18 +
   17.19 +$Id: __init__.py 36457 2004-08-12 15:07:44Z jens $
   17.20 +"""
    18.1 new file mode 100644
    18.2 --- /dev/null
    18.3 +++ b/tests/common.py
    18.4 @@ -0,0 +1,189 @@
    18.5 +##############################################################################
    18.6 +#
    18.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    18.8 +#
    18.9 +# This software is subject to the provisions of the Zope Public License,
   18.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   18.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   18.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   18.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   18.14 +# FOR A PARTICULAR PURPOSE.
   18.15 +#
   18.16 +##############################################################################
   18.17 +""" CMFSetup product:  unit test utilities.
   18.18 +
   18.19 +$Id: common.py 36715 2005-01-17 10:40:01Z yuppie $
   18.20 +"""
   18.21 +
   18.22 +import os
   18.23 +import shutil
   18.24 +from tarfile import TarFile
   18.25 +
   18.26 +from Products.CMFCore.tests.base.testcase import SecurityRequestTest
   18.27 +
   18.28 +
   18.29 +class DOMComparator:
   18.30 +
   18.31 +    def _compareDOM( self, found_text, expected_text, debug=False ):
   18.32 +
   18.33 +        found_lines = [ x.strip() for x in found_text.splitlines() ]
   18.34 +        found_text = '\n'.join( filter( None, found_lines ) )
   18.35 +
   18.36 +        expected_lines = [ x.strip() for x in expected_text.splitlines() ]
   18.37 +        expected_text = '\n'.join( filter( None, expected_lines ) )
   18.38 +
   18.39 +        from xml.dom.minidom import parseString
   18.40 +        found = parseString( found_text )
   18.41 +        expected = parseString( expected_text )
   18.42 +        fxml = found.toxml()
   18.43 +        exml = expected.toxml()
   18.44 +
   18.45 +        if fxml != exml:
   18.46 +
   18.47 +            if debug:
   18.48 +                zipped = zip( fxml, exml )
   18.49 +                diff = [ ( i, zipped[i][0], zipped[i][1] )
   18.50 +                        for i in range( len( zipped ) )
   18.51 +                        if zipped[i][0] != zipped[i][1]
   18.52 +                    ]
   18.53 +                import pdb; pdb.set_trace()
   18.54 +
   18.55 +            print 'Found:'
   18.56 +            print fxml
   18.57 +            print
   18.58 +            print 'Expected:'
   18.59 +            print exml
   18.60 +            print
   18.61 +
   18.62 +        self.assertEqual( found.toxml(), expected.toxml() )
   18.63 +
   18.64 +class BaseRegistryTests( SecurityRequestTest, DOMComparator ):
   18.65 +
   18.66 +    def _makeOne( self, *args, **kw ):
   18.67 +
   18.68 +        # Derived classes must implement _getTargetClass
   18.69 +        return self._getTargetClass()( *args, **kw )
   18.70 +
   18.71 +def _clearTestDirectory( root_path ):
   18.72 +
   18.73 +    if os.path.exists( root_path ):
   18.74 +        shutil.rmtree( root_path )
   18.75 +
   18.76 +def _makeTestFile( filename, root_path, contents ):
   18.77 +
   18.78 +    path, filename = os.path.split( filename )
   18.79 +
   18.80 +    subdir = os.path.join( root_path, path )
   18.81 +
   18.82 +    if not os.path.exists( subdir ):
   18.83 +        os.makedirs( subdir )
   18.84 +
   18.85 +    fqpath = os.path.join( subdir, filename )
   18.86 +
   18.87 +    file = open( fqpath, 'wb' )
   18.88 +    file.write( contents )
   18.89 +    file.close()
   18.90 +    return fqpath
   18.91 +
   18.92 +class FilesystemTestBase( SecurityRequestTest ):
   18.93 +
   18.94 +    def _makeOne( self, *args, **kw ):
   18.95 +
   18.96 +        return self._getTargetClass()( *args, **kw )
   18.97 +
   18.98 +    def setUp( self ):
   18.99 +
  18.100 +        SecurityRequestTest.setUp( self )
  18.101 +        self._clearTempDir()
  18.102 +
  18.103 +    def tearDown( self ):
  18.104 +
  18.105 +        self._clearTempDir()
  18.106 +        SecurityRequestTest.tearDown( self )
  18.107 +
  18.108 +    def _clearTempDir( self ):
  18.109 +
  18.110 +        _clearTestDirectory( self._PROFILE_PATH )
  18.111 +
  18.112 +    def _makeFile( self, filename, contents ):
  18.113 +
  18.114 +        return _makeTestFile( filename, self._PROFILE_PATH, contents )
  18.115 +
  18.116 +
  18.117 +class TarballTester( DOMComparator ):
  18.118 +
  18.119 +    def _verifyTarballContents( self, fileish, toc_list, when=None ):
  18.120 +
  18.121 +        fileish.seek( 0L )
  18.122 +        tarfile = TarFile.open( 'foo.tar.gz', fileobj=fileish, mode='r:gz' )
  18.123 +        items = tarfile.getnames()
  18.124 +        items.sort()
  18.125 +        toc_list.sort()
  18.126 +
  18.127 +        self.assertEqual( len( items ), len( toc_list ) )
  18.128 +        for i in range( len( items ) ):
  18.129 +            self.assertEqual( items[ i ], toc_list[ i ] )
  18.130 +
  18.131 +        if when is not None:
  18.132 +            for tarinfo in tarfile:
  18.133 +                self.failIf( tarinfo.mtime < when )
  18.134 +
  18.135 +    def _verifyTarballEntry( self, fileish, entry_name, data ):
  18.136 +
  18.137 +        fileish.seek( 0L )
  18.138 +        tarfile = TarFile.open( 'foo.tar.gz', fileobj=fileish, mode='r:gz' )
  18.139 +        extract = tarfile.extractfile( entry_name )
  18.140 +        found = extract.read()
  18.141 +        self.assertEqual( found, data )
  18.142 +
  18.143 +    def _verifyTarballEntryXML( self, fileish, entry_name, data ):
  18.144 +
  18.145 +        fileish.seek( 0L )
  18.146 +        tarfile = TarFile.open( 'foo.tar.gz', fileobj=fileish, mode='r:gz' )
  18.147 +        extract = tarfile.extractfile( entry_name )
  18.148 +        found = extract.read()
  18.149 +        self._compareDOM( found, data )
  18.150 +
  18.151 +
  18.152 +class DummyExportContext:
  18.153 +
  18.154 +    def __init__( self, site ):
  18.155 +        self._site = site
  18.156 +        self._wrote = []
  18.157 +
  18.158 +    def getSite( self ):
  18.159 +        return self._site
  18.160 +
  18.161 +    def writeDataFile( self, filename, text, content_type, subdir=None ):
  18.162 +        if subdir is not None:
  18.163 +            filename = '%s/%s' % ( subdir, filename )
  18.164 +        self._wrote.append( ( filename, text, content_type ) )
  18.165 +
  18.166 +class DummyImportContext:
  18.167 +
  18.168 +    def __init__( self, site, purge=True, encoding=None ):
  18.169 +        self._site = site
  18.170 +        self._purge = purge
  18.171 +        self._encoding = encoding
  18.172 +        self._files = {}
  18.173 +
  18.174 +    def getSite( self ):
  18.175 +        return self._site
  18.176 +
  18.177 +    def getEncoding( self ):
  18.178 +        return self._encoding
  18.179 +
  18.180 +    def readDataFile( self, filename, subdir=None ):
  18.181 +
  18.182 +        if subdir is not None:
  18.183 +            filename = '/'.join( (subdir, filename) )
  18.184 +
  18.185 +        return self._files.get( filename )
  18.186 +
  18.187 +    def shouldPurge( self ):
  18.188 +
  18.189 +        return self._purge
  18.190 +
  18.191 +def dummy_handler( context ):
  18.192 +
  18.193 +    pass
    19.1 new file mode 100644
    19.2 --- /dev/null
    19.3 +++ b/tests/conformance.py
    19.4 @@ -0,0 +1,100 @@
    19.5 +##############################################################################
    19.6 +#
    19.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    19.8 +#
    19.9 +# This software is subject to the provisions of the Zope Public License,
   19.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   19.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   19.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   19.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   19.14 +# FOR A PARTICULAR PURPOSE.
   19.15 +#
   19.16 +##############################################################################
   19.17 +""" Base classes for testing interface conformance.
   19.18 +
   19.19 +Derived testcase classes should define '_getTargetClass()', which must
   19.20 +return the class being tested for conformance.
   19.21 +
   19.22 +$Id: conformance.py 36896 2005-04-05 15:17:18Z yuppie $
   19.23 +"""
   19.24 +
   19.25 +class ConformsToISetupContext:
   19.26 +
   19.27 +    def test_ISetupContext_conformance( self ):
   19.28 +
   19.29 +        from Products.CMFSetup.interfaces import ISetupContext
   19.30 +        from Interface.Verify import verifyClass
   19.31 +
   19.32 +        verifyClass( ISetupContext, self._getTargetClass() )
   19.33 +
   19.34 +class ConformsToIImportContext:
   19.35 +
   19.36 +    def test_IImportContext_conformance( self ):
   19.37 +
   19.38 +        from Products.CMFSetup.interfaces import IImportContext
   19.39 +        from Interface.Verify import verifyClass
   19.40 +
   19.41 +        verifyClass( IImportContext, self._getTargetClass() )
   19.42 +
   19.43 +class ConformsToIExportContext:
   19.44 +
   19.45 +    def test_IExportContext_conformance( self ):
   19.46 +
   19.47 +        from Products.CMFSetup.interfaces import IExportContext
   19.48 +        from Interface.Verify import verifyClass
   19.49 +
   19.50 +        verifyClass( IExportContext, self._getTargetClass() )
   19.51 +
   19.52 +class ConformsToIStepRegistry:
   19.53 +
   19.54 +    def test_IStepRegistry_conformance( self ):
   19.55 +
   19.56 +        from Products.CMFSetup.interfaces import IStepRegistry
   19.57 +        from Interface.Verify import verifyClass
   19.58 +
   19.59 +        verifyClass( IStepRegistry, self._getTargetClass() )
   19.60 +
   19.61 +class ConformsToIImportStepRegistry:
   19.62 +
   19.63 +    def test_IImportStepRegistry_conformance( self ):
   19.64 +
   19.65 +        from Products.CMFSetup.interfaces import IImportStepRegistry
   19.66 +        from Interface.Verify import verifyClass
   19.67 +
   19.68 +        verifyClass( IImportStepRegistry, self._getTargetClass() )
   19.69 +
   19.70 +class ConformsToIExportStepRegistry:
   19.71 +
   19.72 +    def test_IExportStepRegistry_conformance( self ):
   19.73 +
   19.74 +        from Products.CMFSetup.interfaces import IExportStepRegistry
   19.75 +        from Interface.Verify import verifyClass
   19.76 +
   19.77 +        verifyClass( IExportStepRegistry, self._getTargetClass() )
   19.78 +
   19.79 +class ConformsToIToolsetRegistry:
   19.80 +
   19.81 +    def test_IToolsetRegistry_conformance( self ):
   19.82 +
   19.83 +        from Products.CMFSetup.interfaces import IToolsetRegistry
   19.84 +        from Interface.Verify import verifyClass
   19.85 +
   19.86 +        verifyClass( IToolsetRegistry, self._getTargetClass() )
   19.87 +
   19.88 +class ConformsToIProfileRegistry:
   19.89 +
   19.90 +    def test_IProfileRegistry_conformance( self ):
   19.91 +
   19.92 +        from Products.CMFSetup.interfaces import IProfileRegistry
   19.93 +        from Interface.Verify import verifyClass
   19.94 +
   19.95 +        verifyClass( IProfileRegistry, self._getTargetClass() )
   19.96 +
   19.97 +class ConformsToISetupTool:
   19.98 +
   19.99 +    def test_ISetupTool_conformance( self ):
  19.100 +
  19.101 +        from Products.CMFSetup.interfaces import ISetupTool
  19.102 +        from Interface.Verify import verifyClass
  19.103 +
  19.104 +        verifyClass( ISetupTool, self._getTargetClass() )
    20.1 new file mode 100644
    20.2 --- /dev/null
    20.3 +++ b/tests/default_profile/export_steps.xml
    20.4 @@ -0,0 +1,8 @@
    20.5 +<?xml version="1.0"?>
    20.6 +<export-steps>
    20.7 + <export-step id="one"
    20.8 +                handler="Products.CMFSetup.tests.common.dummy_handler"
    20.9 +                title="One Step">
   20.10 +  One small step
   20.11 + </export-step>
   20.12 +</export-steps>
    21.1 new file mode 100644
    21.2 --- /dev/null
    21.3 +++ b/tests/default_profile/import_steps.xml
    21.4 @@ -0,0 +1,9 @@
    21.5 +<?xml version="1.0"?>
    21.6 +<import-steps>
    21.7 + <import-step id="one"
    21.8 +             version="1"
    21.9 +             handler="Products.CMFSetup.tests.common.dummy_handler"
   21.10 +             title="One Step">
   21.11 +  One small step
   21.12 + </import-step>
   21.13 +</import-steps>
    22.1 new file mode 100644
    22.2 --- /dev/null
    22.3 +++ b/tests/default_profile/profile.ini
    22.4 @@ -0,0 +1,2 @@
    22.5 +[Metadata]
    22.6 +Title=Unit Test Profile Data
    23.1 new file mode 100644
    23.2 --- /dev/null
    23.3 +++ b/tests/default_profile/toolset.xml
    23.4 @@ -0,0 +1,6 @@
    23.5 +<?xml version="1.0"?>
    23.6 +<tool-setup>
    23.7 + <forbidden tool_id="doomed" />
    23.8 + <required tool_id="mandatory" class="path.to.one" />
    23.9 + <required tool_id="obligatory" class="path.to.another" />
   23.10 +</tool-setup>
    24.1 new file mode 100644
    25.1 new file mode 100644
    26.1 new file mode 100644
    26.2 index 0000000000000000000000000000000000000000..a28a1350ebef24c6f6a393faf5056c07d119cf44
    26.3 GIT binary patch
    26.4 literal 840
    26.5 zc$@)91GoH%P)<h;3K|Lk000e1NJLTq002Ay001or1^@s6@^%PJ00006VoOIv0RI60
    26.6 z0RN!9r;`8x010qNS#tmY3labT3lag+-G2N4000McNliru(h46BC=K~o-Npa_0@X=G
    26.7 zK~#90?O4%q+b|4$>U9jNb12zE-9zmhO7?K}kTZvzJ<tyU&=W}8O)r*nDc_yX6bXR@
    26.8 z2!eP5Dk>@}zBZ)O-vN*bkaX`AU5504jLSdJ6a+c|J%Bb0905E4I4%Fp<82rE5s)50
    26.9 z0-ER2U{X(X)Hmo(_WJ7pnax|D8<GH#O9EqVh#VH9|60i>g7nM!&kIPwU<o_kFXz_V
   26.10 zbC6GKzsseqGwnHB+#H;1(Ix9VnXWN6$_y3R>bq*Nbop+|L4P_C8_b4SB-r9a*5sB&
   26.11 z%|Yc;km<;%QO5_;J8063PO|r|9le-s&BL3%iHr&%BlA{=MTex|7?N{mL`P3yPL2)<
   26.12 zMkN~K_uhN-;!Z>*mdt$NJQ+m?J0=?MixzTZi~ydq;AC%)>w9rME54OF6&-+=?PNHH
   26.13 z@uPaU2k=H?J%A@_zfk*C^=Sw<KXm*T=~Z1ataJKxY0p8|W(|pSY$Er2*;W|<JOa(z
   26.14 zGEwuYO4#65jj}!pEwn)MlMFcNf*(}IcIgD*x3*2bT^8?QZn@!Eb+Kc3tX=wio}=19
   26.15 zo0c1zC^1=bGDEnn=f$5jggMMzCuLvL31H34;X0IwjsS(X-2g6EN`8g+Dk>_@L;C#8
   26.16 z0`N-b)^qO$FW;#2^8_|*E1sLpKVO&oxXGv%_Hs{++#@^gnuSX>s$WUx3VE)Mi~vlb
   26.17 z!qSq`uPd(NBC!Z%>`hT(A^!D)cfQwMr|`?_-B}H_uM%2UOTzk9B@&CE<K~bd_pYC1
   26.18 zCJ$z0q;p(IT&j}`#KQXnz1$sKK>_gMovSlo>y*sE5?Kkv0?2jV1sOFQk+Y}3uZ_!k
   26.19 z$VMH^jb*Fr-*JXKya%FesA0B59{`>4XrJ$vF&w}Ni0N>dKrF-<S{vru6Qu;=WMW!(
   26.20 z#L2sz=b6Sr0dtmEr2Hn>4Sv1c`#jwezH@f(er01TDk>@}Dk>@}Dk>`eBm4or%ud3<
   26.21 Sil1%(0000<MNUMnLSTZVwtlDp
   26.22 
    27.1 new file mode 100644
    27.2 --- /dev/null
    27.3 +++ b/tests/test_actions.py
    27.4 @@ -0,0 +1,442 @@
    27.5 +##############################################################################
    27.6 +#
    27.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    27.8 +#
    27.9 +# This software is subject to the provisions of the Zope Public License,
   27.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   27.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   27.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   27.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   27.14 +# FOR A PARTICULAR PURPOSE.
   27.15 +#
   27.16 +##############################################################################
   27.17 +""" CMFSetup action provider export / import unit tests
   27.18 +
   27.19 +$Id: test_actions.py 37135 2005-07-08 13:24:33Z tseaver $
   27.20 +"""
   27.21 +
   27.22 +import unittest
   27.23 +import Testing
   27.24 +try:
   27.25 +    import Zope2
   27.26 +except ImportError: # BBB: for Zope 2.7
   27.27 +    import Zope as Zope2
   27.28 +Zope2.startup()
   27.29 +
   27.30 +from Acquisition import Implicit
   27.31 +from Acquisition import aq_parent
   27.32 +from OFS.Folder import Folder
   27.33 +from Products.CMFCore.ActionProviderBase import ActionProviderBase
   27.34 +from Products.CMFCore.interfaces.portal_actions \
   27.35 +    import ActionProvider as IActionProvider
   27.36 +
   27.37 +from common import BaseRegistryTests
   27.38 +from common import DummyExportContext
   27.39 +from common import DummyImportContext
   27.40 +
   27.41 +
   27.42 +class DummyTool( Folder, ActionProviderBase ):
   27.43 +
   27.44 +    __implements__ = ( IActionProvider, )
   27.45 +
   27.46 +
   27.47 +class DummyUser( Implicit ):
   27.48 +
   27.49 +    def getId( self ):
   27.50 +        return 'dummy'
   27.51 +
   27.52 +class DummyMembershipTool( DummyTool ):
   27.53 +
   27.54 +    def isAnonymousUser( self ):
   27.55 +        return False
   27.56 +
   27.57 +    def getAuthenticatedMember( self ):
   27.58 +        return DummyUser().__of__( aq_parent( self ) )
   27.59 +
   27.60 +class DummyActionsTool( DummyTool ):
   27.61 +
   27.62 +    def __init__( self ):
   27.63 +
   27.64 +        self._providers = []
   27.65 +
   27.66 +    def addActionProvider( self, provider_name ):
   27.67 +
   27.68 +        self._providers.append( provider_name )
   27.69 +
   27.70 +    def listActionProviders( self ):
   27.71 +
   27.72 +        return self._providers[:]
   27.73 +
   27.74 +    def deleteActionProvider( self, provider_name ):
   27.75 +
   27.76 +        self._providers = [ x for x in self._providers if x != provider_name ]
   27.77 +
   27.78 +class _ActionSetup( BaseRegistryTests ):
   27.79 +
   27.80 +    def _initSite( self, foo=2, bar=2 ):
   27.81 +
   27.82 +        self.root.site = Folder( id='site' )
   27.83 +        site = self.root.site
   27.84 +
   27.85 +        site.portal_membership = DummyMembershipTool()
   27.86 +
   27.87 +        site.portal_actions = DummyActionsTool()
   27.88 +        site.portal_actions.addActionProvider( 'portal_actions' )
   27.89 +
   27.90 +        if foo > 0:
   27.91 +            site.portal_foo = DummyTool()
   27.92 +
   27.93 +        if foo > 1:
   27.94 +            site.portal_foo.addAction( id='foo'
   27.95 +                                    , name='Foo'
   27.96 +                                    , action='foo'
   27.97 +                                    , condition='python:1'
   27.98 +                                    , permission=()
   27.99 +                                    , category='dummy'
  27.100 +                                    , visible=1
  27.101 +                                    )
  27.102 +            site.portal_actions.addActionProvider( 'portal_foo' )
  27.103 +
  27.104 +        if bar > 0:
  27.105 +            site.portal_bar = DummyTool()
  27.106 +
  27.107 +        if bar > 1:
  27.108 +            site.portal_bar.addAction( id='bar'
  27.109 +                                    , name='Bar'
  27.110 +                                    , action='bar'
  27.111 +                                    , condition='python:0'
  27.112 +                                    , permission=( 'Manage portal', )
  27.113 +                                    , category='dummy'
  27.114 +                                    , visible=0
  27.115 +                                    )
  27.116 +            site.portal_actions.addActionProvider( 'portal_bar' )
  27.117 +
  27.118 +        return site
  27.119 +
  27.120 +class ActionProvidersConfiguratorTests( _ActionSetup ):
  27.121 +
  27.122 +    def _getTargetClass( self ):
  27.123 +
  27.124 +        from Products.CMFSetup.actions import ActionProvidersConfigurator
  27.125 +        return ActionProvidersConfigurator
  27.126 +
  27.127 +    def test_listProviderInfo_normal( self ):
  27.128 +
  27.129 +        site = self._initSite()
  27.130 +
  27.131 +        EXPECTED = [ { 'id' : 'portal_actions'
  27.132 +                     , 'actions' : []
  27.133 +                     }
  27.134 +                   , { 'id' : 'portal_foo'
  27.135 +                     , 'actions' : [ { 'id' : 'foo'
  27.136 +                                     , 'title' : 'Foo'
  27.137 +                                     , 'description' : ''
  27.138 +                                     , 'action' : 'string:${object_url}/foo'
  27.139 +                                     , 'condition' : 'python:1'
  27.140 +                                     , 'permissions' : ()
  27.141 +                                     , 'category' : 'dummy'
  27.142 +                                     , 'visible' : True
  27.143 +                                     }
  27.144 +                                   ]
  27.145 +                     }
  27.146 +                   , { 'id' : 'portal_bar'
  27.147 +                     , 'actions' : [ { 'id' : 'bar'
  27.148 +                                     , 'title' : 'Bar'
  27.149 +                                     , 'description' : ''
  27.150 +                                     , 'action' : 'string:${object_url}/bar'
  27.151 +                                     , 'condition' : 'python:0'
  27.152 +                                     , 'permissions' : ('Manage portal',)
  27.153 +                                     , 'category' : 'dummy'
  27.154 +                                     , 'visible' : False
  27.155 +                                     }
  27.156 +                                   ]
  27.157 +                     }
  27.158 +                   ]
  27.159 +
  27.160 +        configurator = self._makeOne( site )
  27.161 +
  27.162 +        info_list = configurator.listProviderInfo()
  27.163 +        self.assertEqual( len( info_list ), len( EXPECTED ) )
  27.164 +
  27.165 +        for found, expected in zip( info_list, EXPECTED ):
  27.166 +            self.assertEqual( found, expected )
  27.167 +
  27.168 +    def test_generateXML_empty( self ):
  27.169 +
  27.170 +        site = self._initSite( 0, 0 )
  27.171 +        configurator = self._makeOne( site ).__of__( site )
  27.172 +        self._compareDOM( configurator.generateXML(), _EMPTY_EXPORT )
  27.173 +
  27.174 +    def test_generateXML_normal( self ):
  27.175 +
  27.176 +        site = self._initSite()
  27.177 +        configurator = self._makeOne( site ).__of__( site )
  27.178 +        self._compareDOM( configurator.generateXML(), _NORMAL_EXPORT )
  27.179 +
  27.180 +
  27.181 +    def test_parseXML_empty( self ):
  27.182 +
  27.183 +        site = self._initSite( 0, 0 )
  27.184 +        configurator = self._makeOne( site )
  27.185 +
  27.186 +        tool_info = configurator.parseXML( _EMPTY_EXPORT )
  27.187 +
  27.188 +        self.assertEqual( len( tool_info[ 'providers' ] ), 1 )
  27.189 +
  27.190 +        info = tool_info[ 'providers' ][ 0 ]
  27.191 +        self.assertEqual( info[ 'id' ], 'portal_actions' )
  27.192 +        self.assertEqual( len( info[ 'actions' ] ), 0 )
  27.193 +
  27.194 +    def test_parseXML_normal( self ):
  27.195 +
  27.196 +        site = self._initSite( 1, 1 )
  27.197 +
  27.198 +        configurator = self._makeOne( site )
  27.199 +        tool_info = configurator.parseXML( _NORMAL_EXPORT )
  27.200 +
  27.201 +        self.assertEqual( len( tool_info['providers'] ), 3 )
  27.202 +
  27.203 +        info = tool_info[ 'providers' ][ 0 ]
  27.204 +        self.assertEqual( info[ 'id' ], 'portal_actions' )
  27.205 +        self.assertEqual( len( info[ 'actions' ] ), 0 )
  27.206 +
  27.207 +        info = tool_info[ 'providers' ][ 1 ]
  27.208 +        self.assertEqual( info[ 'id' ], 'portal_foo' )
  27.209 +        self.assertEqual( len( info[ 'actions' ] ), 1 )
  27.210 +
  27.211 +        action = info[ 'actions' ][ 0 ]
  27.212 +        self.assertEqual( action[ 'id' ], 'foo' )
  27.213 +        self.assertEqual( action[ 'title' ], 'Foo' )
  27.214 +        self.assertEqual( action[ 'action' ]
  27.215 +                        , 'string:${object_url}/foo' )
  27.216 +        self.assertEqual( action[ 'condition' ], 'python:1' )
  27.217 +        self.assertEqual( action[ 'permissions' ], () )
  27.218 +        self.assertEqual( action[ 'category' ], 'dummy' )
  27.219 +        self.assertEqual( action[ 'visible' ], True )
  27.220 +
  27.221 +        info = tool_info[ 'providers' ][ 2 ]
  27.222 +        self.assertEqual( info[ 'id' ], 'portal_bar' )
  27.223 +        self.assertEqual( len( info[ 'actions' ] ), 1 )
  27.224 +
  27.225 +        action = info[ 'actions' ][ 0 ]
  27.226 +        self.assertEqual( action[ 'id' ], 'bar' )
  27.227 +        self.assertEqual( action[ 'title' ], 'Bar' )
  27.228 +        self.assertEqual( action[ 'action' ]
  27.229 +                        , 'string:${object_url}/bar' )
  27.230 +        self.assertEqual( action[ 'condition' ], 'python:0' )
  27.231 +        self.assertEqual( action[ 'permissions' ], ('Manage portal',) )
  27.232 +        self.assertEqual( action[ 'category' ], 'dummy' )
  27.233 +        self.assertEqual( action[ 'visible' ], False )
  27.234 +
  27.235 +
  27.236 +
  27.237 +_EMPTY_EXPORT = """\
  27.238 +<?xml version="1.0"?>
  27.239 +<actions-tool>
  27.240 + <action-provider id="portal_actions">
  27.241 + </action-provider>
  27.242 +</actions-tool>
  27.243 +"""
  27.244 +
  27.245 +_NORMAL_EXPORT = """\
  27.246 +<?xml version="1.0"?>
  27.247 +<actions-tool>
  27.248 + <action-provider id="portal_actions">
  27.249 + </action-provider>
  27.250 + <action-provider id="portal_foo">
  27.251 +  <action action_id="foo"
  27.252 +          title="Foo"
  27.253 +          url_expr="string:${object_url}/foo"
  27.254 +          condition_expr="python:1"
  27.255 +          category="dummy"
  27.256 +          visible="True">
  27.257 +  </action>
  27.258 +</action-provider>
  27.259 + <action-provider id="portal_bar">
  27.260 +  <action action_id="bar"
  27.261 +          title="Bar"
  27.262 +          url_expr="string:${object_url}/bar"
  27.263 +          condition_expr="python:0"
  27.264 +          category="dummy"
  27.265 +          visible="False">
  27.266 +   <permission>Manage portal</permission>
  27.267 +  </action>
  27.268 + </action-provider>
  27.269 +</actions-tool>
  27.270 +"""
  27.271 +
  27.272 +_REMOVE_IMPORT = """\
  27.273 +<?xml version="1.0"?>
  27.274 +<actions-tool>
  27.275 + <action-provider id="portal_actions" remove="">
  27.276 + </action-provider>
  27.277 + <action-provider id="not_existing" remove="">
  27.278 + </action-provider>
  27.279 + <action-provider id="portal_bar" remove="">
  27.280 + </action-provider>
  27.281 +</actions-tool>
  27.282 +"""
  27.283 +
  27.284 +
  27.285 +class Test_exportActionProviders( _ActionSetup ):
  27.286 +
  27.287 +    def test_unchanged( self ):
  27.288 +
  27.289 +        site = self._initSite( 0, 0 )
  27.290 +        context = DummyExportContext( site )
  27.291 +
  27.292 +        from Products.CMFSetup.actions import exportActionProviders
  27.293 +        exportActionProviders( context )
  27.294 +
  27.295 +        self.assertEqual( len( context._wrote ), 1 )
  27.296 +        filename, text, content_type = context._wrote[ 0 ]
  27.297 +        self.assertEqual( filename, 'actions.xml' )
  27.298 +        self._compareDOM( text, _EMPTY_EXPORT )
  27.299 +        self.assertEqual( content_type, 'text/xml' )
  27.300 +
  27.301 +    def test_normal( self ):
  27.302 +
  27.303 +        site = self._initSite()
  27.304 +
  27.305 +        context = DummyExportContext( site )
  27.306 +
  27.307 +        from Products.CMFSetup.actions import exportActionProviders
  27.308 +        exportActionProviders( context )
  27.309 +
  27.310 +        self.assertEqual( len( context._wrote ), 1 )
  27.311 +        filename, text, content_type = context._wrote[ 0 ]
  27.312 +        self.assertEqual( filename, 'actions.xml' )
  27.313 +        self._compareDOM( text, _NORMAL_EXPORT )
  27.314 +        self.assertEqual( content_type, 'text/xml' )
  27.315 +
  27.316 +
  27.317 +class Test_importActionProviders( _ActionSetup ):
  27.318 +
  27.319 +    def test_empty_default_purge( self ):
  27.320 +
  27.321 +        site = self._initSite( 2, 0 )
  27.322 +        atool = site.portal_actions
  27.323 +
  27.324 +        self.assertEqual( len( atool.listActionProviders() ), 2 )
  27.325 +        self.failUnless( 'portal_foo' in atool.listActionProviders() )
  27.326 +        self.failUnless( 'portal_actions' in atool.listActionProviders() )
  27.327 +
  27.328 +        context = DummyImportContext( site )
  27.329 +        context._files[ 'actions.xml' ] = _EMPTY_EXPORT
  27.330 +
  27.331 +        from Products.CMFSetup.actions import importActionProviders
  27.332 +        importActionProviders( context )
  27.333 +
  27.334 +        self.assertEqual( len( atool.listActionProviders() ), 1 )
  27.335 +
  27.336 +    def test_empty_explicit_purge( self ):
  27.337 +
  27.338 +        site = self._initSite( 2, 0 )
  27.339 +        atool = site.portal_actions
  27.340 +
  27.341 +        self.assertEqual( len( atool.listActionProviders() ), 2 )
  27.342 +        self.failUnless( 'portal_foo' in atool.listActionProviders() )
  27.343 +        self.failUnless( 'portal_actions' in atool.listActionProviders() )
  27.344 +
  27.345 +        context = DummyImportContext( site, True )
  27.346 +        context._files[ 'actions.xml' ] = _EMPTY_EXPORT
  27.347 +
  27.348 +        from Products.CMFSetup.actions import importActionProviders
  27.349 +        importActionProviders( context )
  27.350 +
  27.351 +        self.assertEqual( len( atool.listActionProviders() ), 1 )
  27.352 +        self.failIf( 'portal_foo' in atool.listActionProviders() )
  27.353 +        self.failUnless( 'portal_actions' in atool.listActionProviders() )
  27.354 +
  27.355 +    def test_empty_skip_purge( self ):
  27.356 +
  27.357 +        site = self._initSite( 2, 0 )
  27.358 +        atool = site.portal_actions
  27.359 +
  27.360 +        self.assertEqual( len( atool.listActionProviders() ), 2 )
  27.361 +        self.failUnless( 'portal_foo' in atool.listActionProviders() )
  27.362 +        self.failUnless( 'portal_actions' in atool.listActionProviders() )
  27.363 +
  27.364 +        context = DummyImportContext( site, False )
  27.365 +        context._files[ 'actions.xml' ] = _EMPTY_EXPORT
  27.366 +
  27.367 +        from Products.CMFSetup.actions import importActionProviders
  27.368 +        importActionProviders( context )
  27.369 +
  27.370 +        self.assertEqual( len( atool.listActionProviders() ), 2 )
  27.371 +        self.failUnless( 'portal_foo' in atool.listActionProviders() )
  27.372 +        self.failUnless( 'portal_actions' in atool.listActionProviders() )
  27.373 +
  27.374 +    def test_normal( self ):
  27.375 +
  27.376 +        site = self._initSite( 1, 1 )
  27.377 +        atool = site.portal_actions
  27.378 +        foo = site.portal_foo
  27.379 +        bar = site.portal_bar
  27.380 +
  27.381 +        self.assertEqual( len( atool.listActionProviders() ), 1 )
  27.382 +        self.failIf( 'portal_foo' in atool.listActionProviders() )
  27.383 +        self.failIf( foo.listActions() )
  27.384 +        self.failIf( 'portal_bar' in atool.listActionProviders() )
  27.385 +        self.failIf( bar.listActions() )
  27.386 +        self.failUnless( 'portal_actions' in atool.listActionProviders() )
  27.387 +
  27.388 +        context = DummyImportContext( site )
  27.389 +        context._files[ 'actions.xml' ] = _NORMAL_EXPORT
  27.390 +
  27.391 +        from Products.CMFSetup.actions import importActionProviders
  27.392 +        importActionProviders( context )
  27.393 +
  27.394 +        self.assertEqual( len( atool.listActionProviders() ), 3 )
  27.395 +        self.failUnless( 'portal_foo' in atool.listActionProviders() )
  27.396 +        self.failUnless( foo.listActions() )
  27.397 +        self.failUnless( 'portal_bar' in atool.listActionProviders() )
  27.398 +        self.failUnless( bar.listActions() )
  27.399 +        self.failUnless( 'portal_actions' in atool.listActionProviders() )
  27.400 +
  27.401 +    def test_normal_encode_as_ascii( self ):
  27.402 +
  27.403 +        site = self._initSite( 1, 1 )
  27.404 +        atool = site.portal_actions
  27.405 +        foo = site.portal_foo
  27.406 +        bar = site.portal_bar
  27.407 +
  27.408 +        context = DummyImportContext( site, encoding='ascii' )
  27.409 +        context._files[ 'actions.xml' ] = _NORMAL_EXPORT
  27.410 +
  27.411 +        from Products.CMFSetup.actions import importActionProviders
  27.412 +        importActionProviders( context )
  27.413 +
  27.414 +        self.assertEqual( len( atool.listActionProviders() ), 3 )
  27.415 +        self.failUnless( 'portal_foo' in atool.listActionProviders() )
  27.416 +        self.failUnless( foo.listActions() )
  27.417 +        self.failUnless( 'portal_bar' in atool.listActionProviders() )
  27.418 +        self.failUnless( bar.listActions() )
  27.419 +        self.failUnless( 'portal_actions' in atool.listActionProviders() )
  27.420 +
  27.421 +    def test_remove_skip_purge(self):
  27.422 +
  27.423 +        from Products.CMFSetup.actions import importActionProviders
  27.424 +
  27.425 +        site = self._initSite(2, 2)
  27.426 +        atool = site.portal_actions
  27.427 +
  27.428 +        self.assertEqual( atool.listActionProviders(),
  27.429 +                          ['portal_actions', 'portal_foo', 'portal_bar'] )
  27.430 +
  27.431 +        context = DummyImportContext(site, False)
  27.432 +        context._files['actions.xml'] = _REMOVE_IMPORT
  27.433 +        importActionProviders(context)
  27.434 +
  27.435 +        self.assertEqual( atool.listActionProviders(), ['portal_foo'] )
  27.436 +
  27.437 +
  27.438 +def test_suite():
  27.439 +    return unittest.TestSuite((
  27.440 +        unittest.makeSuite( ActionProvidersConfiguratorTests ),
  27.441 +        unittest.makeSuite( Test_exportActionProviders ),
  27.442 +        unittest.makeSuite( Test_importActionProviders ),
  27.443 +        ))
  27.444 +
  27.445 +if __name__ == '__main__':
  27.446 +    unittest.main(defaultTest='test_suite')
    28.1 new file mode 100644
    28.2 --- /dev/null
    28.3 +++ b/tests/test_all.py
    28.4 @@ -0,0 +1,51 @@
    28.5 +##############################################################################
    28.6 +#
    28.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    28.8 +#
    28.9 +# This software is subject to the provisions of the Zope Public License,
   28.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   28.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   28.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   28.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   28.14 +# FOR A PARTICULAR PURPOSE.
   28.15 +#
   28.16 +##############################################################################
   28.17 +""" CMFSetup tests.
   28.18 +
   28.19 +$Id: test_all.py 37135 2005-07-08 13:24:33Z tseaver $
   28.20 +"""
   28.21 +
   28.22 +from unittest import main
   28.23 +import Testing
   28.24 +try:
   28.25 +    import Zope2
   28.26 +except ImportError: # BBB: for Zope 2.7
   28.27 +    import Zope as Zope2
   28.28 +Zope2.startup()
   28.29 +
   28.30 +from Products.CMFCore.tests.base.utils import build_test_suite
   28.31 +
   28.32 +
   28.33 +def suite():
   28.34 +    return build_test_suite( 'Products.CMFSetup.tests'
   28.35 +                           , [ 'test_actions'
   28.36 +                             , 'test_context'
   28.37 +                             , 'test_differ'
   28.38 +                             , 'test_properties'
   28.39 +                             , 'test_registry'
   28.40 +                             , 'test_rolemap'
   28.41 +                             , 'test_skins'
   28.42 +                             , 'test_tool'
   28.43 +                             , 'test_typeinfo'
   28.44 +                             , 'test_utils'
   28.45 +                             , 'test_workflow'
   28.46 +                             ]
   28.47 +                           )
   28.48 +
   28.49 +def test_suite():
   28.50 +    # Just to silence the top-level test.py
   28.51 +    return None
   28.52 +
   28.53 +if __name__ == '__main__':
   28.54 +    main(defaultTest='suite')
   28.55 +
    29.1 new file mode 100644
    29.2 --- /dev/null
    29.3 +++ b/tests/test_context.py
    29.4 @@ -0,0 +1,991 @@
    29.5 +##############################################################################
    29.6 +#
    29.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    29.8 +#
    29.9 +# This software is subject to the provisions of the Zope Public License,
   29.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   29.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   29.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   29.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   29.14 +# FOR A PARTICULAR PURPOSE.
   29.15 +#
   29.16 +##############################################################################
   29.17 +""" Unit tests for import / export contexts.
   29.18 +
   29.19 +$Id: test_context.py 37351 2005-07-20 21:16:59Z jens $
   29.20 +"""
   29.21 +
   29.22 +import unittest
   29.23 +import Testing
   29.24 +try:
   29.25 +    import Zope2
   29.26 +except ImportError: # BBB: for Zope 2.7
   29.27 +    import Zope as Zope2
   29.28 +Zope2.startup()
   29.29 +
   29.30 +import os
   29.31 +import time
   29.32 +from StringIO import StringIO
   29.33 +
   29.34 +from DateTime.DateTime import DateTime
   29.35 +from OFS.Folder import Folder
   29.36 +from OFS.Image import File
   29.37 +
   29.38 +from Products.CMFCore.tests.base.testcase import SecurityRequestTest
   29.39 +
   29.40 +from common import FilesystemTestBase
   29.41 +from common import TarballTester
   29.42 +from common import _makeTestFile
   29.43 +from conformance import ConformsToISetupContext
   29.44 +from conformance import ConformsToIImportContext
   29.45 +from conformance import ConformsToIExportContext
   29.46 +
   29.47 +
   29.48 +class DummySite( Folder ):
   29.49 +
   29.50 +    pass
   29.51 +
   29.52 +class DummyTool( Folder ):
   29.53 +
   29.54 +    pass
   29.55 +
   29.56 +class DirectoryImportContextTests( FilesystemTestBase
   29.57 +                                 , ConformsToISetupContext
   29.58 +                                 , ConformsToIImportContext
   29.59 +                                 ):
   29.60 +
   29.61 +    _PROFILE_PATH = '/tmp/ICTTexts'
   29.62 +
   29.63 +    def _getTargetClass( self ):
   29.64 +
   29.65 +        from Products.CMFSetup.context import DirectoryImportContext
   29.66 +        return DirectoryImportContext
   29.67 +
   29.68 +    def test_readDataFile_nonesuch( self ):
   29.69 +
   29.70 +        FILENAME = 'nonesuch.txt'
   29.71 +
   29.72 +        site = DummySite( 'site' ).__of__( self.root )
   29.73 +        ctx = self._makeOne( site, self._PROFILE_PATH )
   29.74 +
   29.75 +        self.assertEqual( ctx.readDataFile( FILENAME ), None )
   29.76 +
   29.77 +    def test_readDataFile_simple( self ):
   29.78 +
   29.79 +        from string import printable
   29.80 +
   29.81 +        FILENAME = 'simple.txt'
   29.82 +        self._makeFile( FILENAME, printable )
   29.83 +
   29.84 +        site = DummySite( 'site' ).__of__( self.root )
   29.85 +        ctx = self._makeOne( site, self._PROFILE_PATH )
   29.86 +
   29.87 +        self.assertEqual( ctx.readDataFile( FILENAME ), printable )
   29.88 +
   29.89 +    def test_readDataFile_subdir( self ):
   29.90 +
   29.91 +        from string import printable
   29.92 +
   29.93 +        FILENAME = 'subdir/nested.txt'
   29.94 +        self._makeFile( FILENAME, printable )
   29.95 +
   29.96 +        site = DummySite( 'site' ).__of__( self.root )
   29.97 +        ctx = self._makeOne( site, self._PROFILE_PATH )
   29.98 +
   29.99 +        self.assertEqual( ctx.readDataFile( FILENAME ), printable )
  29.100 +
  29.101 +    def test_getLastModified_nonesuch( self ):
  29.102 +
  29.103 +        FILENAME = 'nonesuch.txt'
  29.104 +
  29.105 +        site = DummySite( 'site' ).__of__( self.root )
  29.106 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.107 +
  29.108 +        self.assertEqual( ctx.getLastModified( FILENAME ), None )
  29.109 +
  29.110 +    def test_getLastModified_simple( self ):
  29.111 +
  29.112 +        from string import printable
  29.113 +
  29.114 +        FILENAME = 'simple.txt'
  29.115 +        fqpath = self._makeFile( FILENAME, printable )
  29.116 +        timestamp = os.path.getmtime( fqpath )
  29.117 +
  29.118 +        site = DummySite( 'site' ).__of__( self.root )
  29.119 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.120 +
  29.121 +        lm = ctx.getLastModified( FILENAME )
  29.122 +        self.failUnless( isinstance( lm, DateTime ) )
  29.123 +        self.assertEqual( lm, timestamp )
  29.124 +
  29.125 +    def test_getLastModified_subdir( self ):
  29.126 +
  29.127 +        from string import printable
  29.128 +
  29.129 +        SUBDIR = 'subdir'
  29.130 +        FILENAME = os.path.join( SUBDIR, 'nested.txt' )
  29.131 +        fqpath = self._makeFile( FILENAME, printable )
  29.132 +        timestamp = os.path.getmtime( fqpath )
  29.133 +
  29.134 +        site = DummySite( 'site' ).__of__( self.root )
  29.135 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.136 +
  29.137 +        lm = ctx.getLastModified( FILENAME )
  29.138 +        self.failUnless( isinstance( lm, DateTime ) )
  29.139 +        self.assertEqual( lm, timestamp )
  29.140 +
  29.141 +    def test_getLastModified_directory( self ):
  29.142 +
  29.143 +        from string import printable
  29.144 +
  29.145 +        SUBDIR = 'subdir'
  29.146 +        FILENAME = os.path.join( SUBDIR, 'nested.txt' )
  29.147 +        fqpath = self._makeFile( FILENAME, printable )
  29.148 +        path, file = os.path.split( fqpath )
  29.149 +        timestamp = os.path.getmtime( path )
  29.150 +
  29.151 +        site = DummySite( 'site' ).__of__( self.root )
  29.152 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.153 +
  29.154 +        lm = ctx.getLastModified( SUBDIR )
  29.155 +        self.failUnless( isinstance( lm, DateTime ) )
  29.156 +        self.assertEqual( lm, timestamp )
  29.157 +
  29.158 +    def test_isDirectory_nonesuch( self ):
  29.159 +
  29.160 +        FILENAME = 'nonesuch.txt'
  29.161 +
  29.162 +        site = DummySite( 'site' ).__of__( self.root )
  29.163 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.164 +
  29.165 +        self.assertEqual( ctx.isDirectory( FILENAME ), None )
  29.166 +
  29.167 +    def test_isDirectory_simple( self ):
  29.168 +
  29.169 +        from string import printable
  29.170 +
  29.171 +        FILENAME = 'simple.txt'
  29.172 +        fqpath = self._makeFile( FILENAME, printable )
  29.173 +
  29.174 +        site = DummySite( 'site' ).__of__( self.root )
  29.175 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.176 +
  29.177 +        self.assertEqual( ctx.isDirectory( FILENAME ), False )
  29.178 +
  29.179 +    def test_isDirectory_nested( self ):
  29.180 +
  29.181 +        from string import printable
  29.182 +
  29.183 +        SUBDIR = 'subdir'
  29.184 +        FILENAME = os.path.join( SUBDIR, 'nested.txt' )
  29.185 +        fqpath = self._makeFile( FILENAME, printable )
  29.186 +
  29.187 +        site = DummySite( 'site' ).__of__( self.root )
  29.188 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.189 +
  29.190 +        self.assertEqual( ctx.isDirectory( FILENAME ), False )
  29.191 +
  29.192 +    def test_isDirectory_directory( self ):
  29.193 +
  29.194 +        from string import printable
  29.195 +
  29.196 +        SUBDIR = 'subdir'
  29.197 +        FILENAME = os.path.join( SUBDIR, 'nested.txt' )
  29.198 +        fqpath = self._makeFile( FILENAME, printable )
  29.199 +
  29.200 +        site = DummySite( 'site' ).__of__( self.root )
  29.201 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.202 +
  29.203 +        self.assertEqual( ctx.isDirectory( SUBDIR ), True )
  29.204 +
  29.205 +    def test_listDirectory_nonesuch( self ):
  29.206 +
  29.207 +        FILENAME = 'nonesuch.txt'
  29.208 +
  29.209 +        site = DummySite( 'site' ).__of__( self.root )
  29.210 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.211 +
  29.212 +        self.assertEqual( ctx.listDirectory( FILENAME ), None )
  29.213 +
  29.214 +    def test_listDirectory_root( self ):
  29.215 +
  29.216 +        from string import printable
  29.217 +
  29.218 +        site = DummySite( 'site' ).__of__( self.root )
  29.219 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.220 +
  29.221 +        FILENAME = 'simple.txt'
  29.222 +        self._makeFile( FILENAME, printable )
  29.223 +
  29.224 +        self.assertEqual( len( ctx.listDirectory( None ) ), 1 )
  29.225 +        self.failUnless( FILENAME in ctx.listDirectory( None ) )
  29.226 +
  29.227 +    def test_listDirectory_simple( self ):
  29.228 +
  29.229 +        from string import printable
  29.230 +
  29.231 +        FILENAME = 'simple.txt'
  29.232 +        self._makeFile( FILENAME, printable )
  29.233 +
  29.234 +        site = DummySite( 'site' ).__of__( self.root )
  29.235 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.236 +
  29.237 +        self.assertEqual( ctx.listDirectory( FILENAME ), None )
  29.238 +
  29.239 +    def test_listDirectory_nested( self ):
  29.240 +
  29.241 +        from string import printable
  29.242 +
  29.243 +        SUBDIR = 'subdir'
  29.244 +        FILENAME = os.path.join( SUBDIR, 'nested.txt' )
  29.245 +        self._makeFile( FILENAME, printable )
  29.246 +
  29.247 +        site = DummySite( 'site' ).__of__( self.root )
  29.248 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.249 +
  29.250 +        self.assertEqual( ctx.listDirectory( FILENAME ), None )
  29.251 +
  29.252 +    def test_listDirectory_single( self ):
  29.253 +
  29.254 +        from string import printable
  29.255 +
  29.256 +        SUBDIR = 'subdir'
  29.257 +        FILENAME = os.path.join( SUBDIR, 'nested.txt' )
  29.258 +        self._makeFile( FILENAME, printable )
  29.259 +
  29.260 +        site = DummySite( 'site' ).__of__( self.root )
  29.261 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.262 +
  29.263 +        names = ctx.listDirectory( SUBDIR )
  29.264 +        self.assertEqual( len( names ), 1 )
  29.265 +        self.failUnless( 'nested.txt' in names )
  29.266 +
  29.267 +    def test_listDirectory_multiple( self ):
  29.268 +
  29.269 +        from string import printable
  29.270 +        SUBDIR = 'subdir'
  29.271 +        FILENAME = os.path.join( SUBDIR, 'nested.txt' )
  29.272 +        self._makeFile( FILENAME, printable )
  29.273 +        self._makeFile( os.path.join( SUBDIR, 'another.txt' ), 'ABC' )
  29.274 +
  29.275 +        site = DummySite( 'site' ).__of__( self.root )
  29.276 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.277 +
  29.278 +        names = ctx.listDirectory( SUBDIR )
  29.279 +        self.assertEqual( len( names ), 2 )
  29.280 +        self.failUnless( 'nested.txt' in names )
  29.281 +        self.failUnless( 'another.txt' in names )
  29.282 +
  29.283 +    def test_listDirectory_skip_implicit( self ):
  29.284 +
  29.285 +        from string import printable
  29.286 +        SUBDIR = 'subdir'
  29.287 +        FILENAME = os.path.join( SUBDIR, 'nested.txt' )
  29.288 +        self._makeFile( FILENAME, printable )
  29.289 +        self._makeFile( os.path.join( SUBDIR, 'another.txt' ), 'ABC' )
  29.290 +        self._makeFile( os.path.join( SUBDIR, 'CVS/skip.txt' ), 'DEF' )
  29.291 +        self._makeFile( os.path.join( SUBDIR, '.svn/skip.txt' ), 'GHI' )
  29.292 +
  29.293 +        site = DummySite( 'site' ).__of__( self.root )
  29.294 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.295 +
  29.296 +        names = ctx.listDirectory( SUBDIR )
  29.297 +        self.assertEqual( len( names ), 2 )
  29.298 +        self.failUnless( 'nested.txt' in names )
  29.299 +        self.failUnless( 'another.txt' in names )
  29.300 +        self.failIf( 'CVS' in names )
  29.301 +        self.failIf( '.svn' in names )
  29.302 +
  29.303 +    def test_listDirectory_skip_explicit( self ):
  29.304 +
  29.305 +        from string import printable
  29.306 +        SUBDIR = 'subdir'
  29.307 +        FILENAME = os.path.join( SUBDIR, 'nested.txt' )
  29.308 +        self._makeFile( FILENAME, printable )
  29.309 +        self._makeFile( os.path.join( SUBDIR, 'another.txt' ), 'ABC' )
  29.310 +        self._makeFile( os.path.join( SUBDIR, 'CVS/skip.txt' ), 'DEF' )
  29.311 +        self._makeFile( os.path.join( SUBDIR, '.svn/skip.txt' ), 'GHI' )
  29.312 +
  29.313 +        site = DummySite( 'site' ).__of__( self.root )
  29.314 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.315 +
  29.316 +        names = ctx.listDirectory( SUBDIR, ( 'nested.txt', ) )
  29.317 +        self.assertEqual( len( names ), 3 )
  29.318 +        self.failIf( 'nested.txt' in names )
  29.319 +        self.failUnless( 'another.txt' in names )
  29.320 +        self.failUnless( 'CVS' in names )
  29.321 +        self.failUnless( '.svn' in names )
  29.322 +
  29.323 +class DirectoryExportContextTests( FilesystemTestBase
  29.324 +                                 , ConformsToISetupContext
  29.325 +                                 , ConformsToIExportContext
  29.326 +                                 ):
  29.327 +
  29.328 +    _PROFILE_PATH = '/tmp/ECTTexts'
  29.329 +
  29.330 +    def _getTargetClass( self ):
  29.331 +
  29.332 +        from Products.CMFSetup.context import DirectoryExportContext
  29.333 +        return DirectoryExportContext
  29.334 +
  29.335 +    def test_writeDataFile_simple( self ):
  29.336 +
  29.337 +        from string import printable, digits
  29.338 +        FILENAME = 'simple.txt'
  29.339 +        fqname = self._makeFile( FILENAME, printable )
  29.340 +
  29.341 +        site = DummySite( 'site' ).__of__( self.root )
  29.342 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.343 +
  29.344 +        ctx.writeDataFile( FILENAME, digits, 'text/plain' )
  29.345 +
  29.346 +        self.assertEqual( open( fqname, 'rb' ).read(), digits )
  29.347 +
  29.348 +    def test_writeDataFile_new_subdir( self ):
  29.349 +
  29.350 +        from string import printable, digits
  29.351 +        SUBDIR = 'subdir'
  29.352 +        FILENAME = 'nested.txt'
  29.353 +        fqname = os.path.join( self._PROFILE_PATH, SUBDIR, FILENAME )
  29.354 +
  29.355 +        site = DummySite( 'site' ).__of__( self.root )
  29.356 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.357 +
  29.358 +        ctx.writeDataFile( FILENAME, digits, 'text/plain', SUBDIR )
  29.359 +
  29.360 +        self.assertEqual( open( fqname, 'rb' ).read(), digits )
  29.361 +
  29.362 +    def test_writeDataFile_overwrite( self ):
  29.363 +
  29.364 +        from string import printable, digits
  29.365 +        SUBDIR = 'subdir'
  29.366 +        FILENAME = 'nested.txt'
  29.367 +        fqname = self._makeFile( os.path.join( SUBDIR, FILENAME )
  29.368 +                               , printable )
  29.369 +
  29.370 +        site = DummySite( 'site' ).__of__( self.root )
  29.371 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.372 +
  29.373 +        ctx.writeDataFile( FILENAME, digits, 'text/plain', SUBDIR )
  29.374 +
  29.375 +        self.assertEqual( open( fqname, 'rb' ).read(), digits )
  29.376 +
  29.377 +    def test_writeDataFile_existing_subdir( self ):
  29.378 +
  29.379 +        from string import printable, digits
  29.380 +        SUBDIR = 'subdir'
  29.381 +        FILENAME = 'nested.txt'
  29.382 +        self._makeFile( os.path.join( SUBDIR, 'another.txt' ), printable )
  29.383 +        fqname = os.path.join( self._PROFILE_PATH, SUBDIR, FILENAME )
  29.384 +
  29.385 +        site = DummySite( 'site' ).__of__( self.root )
  29.386 +        ctx = self._makeOne( site, self._PROFILE_PATH )
  29.387 +
  29.388 +        ctx.writeDataFile( FILENAME, digits, 'text/plain', SUBDIR )
  29.389 +
  29.390 +        self.assertEqual( open( fqname, 'rb' ).read(), digits )
  29.391 +
  29.392 +
  29.393 +class TarballExportContextTests( FilesystemTestBase
  29.394 +                               , TarballTester
  29.395 +                               , ConformsToISetupContext
  29.396 +                               , ConformsToIExportContext
  29.397 +                               ):
  29.398 +
  29.399 +    _PROFILE_PATH = '/tmp/TECT_tests'
  29.400 +
  29.401 +    def _getTargetClass( self ):
  29.402 +
  29.403 +        from Products.CMFSetup.context import TarballExportContext
  29.404 +        return TarballExportContext
  29.405 +
  29.406 +    def test_writeDataFile_simple( self ):
  29.407 +
  29.408 +        from string import printable
  29.409 +        now = long( time.time() )
  29.410 +
  29.411 +        site = DummySite( 'site' ).__of__( self.root )
  29.412 +        ctx = self._getTargetClass()( site )
  29.413 +
  29.414 +        ctx.writeDataFile( 'foo.txt', printable, 'text/plain' )
  29.415 +
  29.416 +        fileish = StringIO( ctx.getArchive() )
  29.417 +
  29.418 +        self._verifyTarballContents( fileish, [ 'foo.txt' ], now )
  29.419 +        self._verifyTarballEntry( fileish, 'foo.txt', printable )
  29.420 +
  29.421 +    def test_writeDataFile_multiple( self ):
  29.422 +
  29.423 +        from string import printable
  29.424 +        from string import digits
  29.425 +
  29.426 +        site = DummySite( 'site' ).__of__( self.root )
  29.427 +        ctx = self._getTargetClass()( site )
  29.428 +
  29.429 +        ctx.writeDataFile( 'foo.txt', printable, 'text/plain' )
  29.430 +        ctx.writeDataFile( 'bar.txt', digits, 'text/plain' )
  29.431 +
  29.432 +        fileish = StringIO( ctx.getArchive() )
  29.433 +
  29.434 +        self._verifyTarballContents( fileish, [ 'foo.txt', 'bar.txt' ] )
  29.435 +        self._verifyTarballEntry( fileish, 'foo.txt', printable )
  29.436 +        self._verifyTarballEntry( fileish, 'bar.txt', digits )
  29.437 +
  29.438 +    def test_writeDataFile_subdir( self ):
  29.439 +
  29.440 +        from string import printable
  29.441 +        from string import digits
  29.442 +
  29.443 +        site = DummySite( 'site' ).__of__( self.root )
  29.444 +        ctx = self._getTargetClass()( site )
  29.445 +
  29.446 +        ctx.writeDataFile( 'foo.txt', printable, 'text/plain' )
  29.447 +        ctx.writeDataFile( 'bar/baz.txt', digits, 'text/plain' )
  29.448 +
  29.449 +        fileish = StringIO( ctx.getArchive() )
  29.450 +
  29.451 +        self._verifyTarballContents( fileish, [ 'foo.txt', 'bar/baz.txt' ] )
  29.452 +        self._verifyTarballEntry( fileish, 'foo.txt', printable )
  29.453 +        self._verifyTarballEntry( fileish, 'bar/baz.txt', digits )
  29.454 +
  29.455 +
  29.456 +class SnapshotExportContextTests( SecurityRequestTest
  29.457 +                                , ConformsToISetupContext
  29.458 +                                , ConformsToIExportContext
  29.459 +                                ):
  29.460 +
  29.461 +    def _getTargetClass( self ):
  29.462 +
  29.463 +        from Products.CMFSetup.context import SnapshotExportContext
  29.464 +        return SnapshotExportContext
  29.465 +
  29.466 +    def _makeOne( self, *args, **kw ):
  29.467 +
  29.468 +        return self._getTargetClass()( *args, **kw )
  29.469 +
  29.470 +    def test_writeDataFile_simple_image( self ):
  29.471 +
  29.472 +        from OFS.Image import Image
  29.473 +        FILENAME = 'simple.txt'
  29.474 +        _CONTENT_TYPE = 'image/png'
  29.475 +        png_filename = os.path.join( os.path.split( __file__ )[0]
  29.476 +                                   , 'simple.png' )
  29.477 +        png_file = open( png_filename, 'rb' )
  29.478 +        png_data = png_file.read()
  29.479 +        png_file.close()
  29.480 +
  29.481 +        site = DummySite( 'site' ).__of__( self.root )
  29.482 +        site.portal_setup = DummyTool( 'portal_setup' )
  29.483 +        tool = site.portal_setup
  29.484 +        ctx = self._makeOne( tool, 'simple' )
  29.485 +
  29.486 +        ctx.writeDataFile( FILENAME, png_data, _CONTENT_TYPE )
  29.487 +
  29.488 +        snapshot = tool.snapshots._getOb( 'simple' )
  29.489 +
  29.490 +        self.assertEqual( len( snapshot.objectIds() ), 1 )
  29.491 +        self.failUnless( FILENAME in snapshot.objectIds() )
  29.492 +
  29.493 +        fileobj = snapshot._getOb( FILENAME )
  29.494 +
  29.495 +        self.assertEqual( fileobj.getId(), FILENAME )
  29.496 +        self.assertEqual( fileobj.meta_type, Image.meta_type )
  29.497 +        self.assertEqual( fileobj.getContentType(), _CONTENT_TYPE )
  29.498 +        self.assertEqual( fileobj.data, png_data )
  29.499 +
  29.500 +    def test_writeDataFile_simple_plain_text( self ):
  29.501 +
  29.502 +        from string import digits
  29.503 +        from OFS.Image import File
  29.504 +        FILENAME = 'simple.txt'
  29.505 +        _CONTENT_TYPE = 'text/plain'
  29.506 +
  29.507 +        site = DummySite( 'site' ).__of__( self.root )
  29.508 +        site.portal_setup = DummyTool( 'portal_setup' )
  29.509 +        tool = site.portal_setup
  29.510 +        ctx = self._makeOne( tool, 'simple' )
  29.511 +
  29.512 +        ctx.writeDataFile( FILENAME, digits, _CONTENT_TYPE )
  29.513 +
  29.514 +        snapshot = tool.snapshots._getOb( 'simple' )
  29.515 +
  29.516 +        self.assertEqual( len( snapshot.objectIds() ), 1 )
  29.517 +        self.failUnless( FILENAME in snapshot.objectIds() )
  29.518 +
  29.519 +        fileobj = snapshot._getOb( FILENAME )
  29.520 +
  29.521 +        self.assertEqual( fileobj.getId(), FILENAME )
  29.522 +        self.assertEqual( fileobj.meta_type, File.meta_type )
  29.523 +        self.assertEqual( fileobj.getContentType(), _CONTENT_TYPE )
  29.524 +        self.assertEqual( str( fileobj ), digits )
  29.525 +
  29.526 +    def test_writeDataFile_simple_xml( self ):
  29.527 +
  29.528 +        from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
  29.529 +        FILENAME = 'simple.xml'
  29.530 +        _CONTENT_TYPE = 'text/xml'
  29.531 +        _XML = """<?xml version="1.0"?><simple />"""
  29.532 +
  29.533 +        site = DummySite( 'site' ).__of__( self.root )
  29.534 +        site.portal_setup = DummyTool( 'portal_setup' )
  29.535 +        tool = site.portal_setup
  29.536 +        ctx = self._makeOne( tool, 'simple' )
  29.537 +
  29.538 +        ctx.writeDataFile( FILENAME, _XML, _CONTENT_TYPE )
  29.539 +
  29.540 +        snapshot = tool.snapshots._getOb( 'simple' )
  29.541 +
  29.542 +        self.assertEqual( len( snapshot.objectIds() ), 1 )
  29.543 +        self.failUnless( FILENAME in snapshot.objectIds() )
  29.544 +
  29.545 +        template = snapshot._getOb( FILENAME )
  29.546 +
  29.547 +        self.assertEqual( template.getId(), FILENAME )
  29.548 +        self.assertEqual( template.meta_type, ZopePageTemplate.meta_type )
  29.549 +        self.assertEqual( template.read(), _XML )
  29.550 +        self.failIf( template.html() )
  29.551 +
  29.552 +    def test_writeDataFile_unicode_xml( self ):
  29.553 +
  29.554 +        from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
  29.555 +        FILENAME = 'simple.xml'
  29.556 +        _CONTENT_TYPE = 'text/xml'
  29.557 +        _XML = u"""<?xml version="1.0"?><simple />"""
  29.558 +
  29.559 +        site = DummySite( 'site' ).__of__( self.root )
  29.560 +        site.portal_setup = DummyTool( 'portal_setup' )
  29.561 +        tool = site.portal_setup
  29.562 +        ctx = self._makeOne( tool, 'simple' )
  29.563 +
  29.564 +        ctx.writeDataFile( FILENAME, _XML, _CONTENT_TYPE )
  29.565 +
  29.566 +        snapshot = tool.snapshots._getOb( 'simple' )
  29.567 +
  29.568 +        self.assertEqual( len( snapshot.objectIds() ), 1 )
  29.569 +        self.failUnless( FILENAME in snapshot.objectIds() )
  29.570 +
  29.571 +        template = snapshot._getOb( FILENAME )
  29.572 +
  29.573 +        self.assertEqual( template.getId(), FILENAME )
  29.574 +        self.assertEqual( template.meta_type, ZopePageTemplate.meta_type )
  29.575 +        self.assertEqual( template.read(), _XML )
  29.576 +        self.failIf( template.html() )
  29.577 +
  29.578 +    def test_writeDataFile_subdir_dtml( self ):
  29.579 +
  29.580 +        from OFS.DTMLDocument import DTMLDocument
  29.581 +        FILENAME = 'simple.dtml'
  29.582 +        _CONTENT_TYPE = 'text/html'
  29.583 +        _HTML = """<html><body><h1>HTML</h1></body></html>"""
  29.584 +
  29.585 +        site = DummySite( 'site' ).__of__( self.root )
  29.586 +        site.portal_setup = DummyTool( 'portal_setup' )
  29.587 +        tool = site.portal_setup
  29.588 +        ctx = self._makeOne( tool, 'simple' )
  29.589 +
  29.590 +        ctx.writeDataFile( FILENAME, _HTML, _CONTENT_TYPE, 'sub1' )
  29.591 +
  29.592 +        snapshot = tool.snapshots._getOb( 'simple' )
  29.593 +        sub1 = snapshot._getOb( 'sub1' )
  29.594 +
  29.595 +        self.assertEqual( len( sub1.objectIds() ), 1 )
  29.596 +        self.failUnless( FILENAME in sub1.objectIds() )
  29.597 +
  29.598 +        template = sub1._getOb( FILENAME )
  29.599 +
  29.600 +        self.assertEqual( template.getId(), FILENAME )
  29.601 +        self.assertEqual( template.meta_type, DTMLDocument.meta_type )
  29.602 +        self.assertEqual( template.read(), _HTML )
  29.603 +
  29.604 +    def test_writeDataFile_nested_subdirs_html( self ):
  29.605 +
  29.606 +        from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
  29.607 +        FILENAME = 'simple.html'
  29.608 +        _CONTENT_TYPE = 'text/html'
  29.609 +        _HTML = """<html><body><h1>HTML</h1></body></html>"""
  29.610 +
  29.611 +        site = DummySite( 'site' ).__of__( self.root )
  29.612 +        site.portal_setup = DummyTool( 'portal_setup' )
  29.613 +        tool = site.portal_setup
  29.614 +        ctx = self._makeOne( tool, 'simple' )
  29.615 +
  29.616 +        ctx.writeDataFile( FILENAME, _HTML, _CONTENT_TYPE, 'sub1/sub2' )
  29.617 +
  29.618 +        snapshot = tool.snapshots._getOb( 'simple' )
  29.619 +        sub1 = snapshot._getOb( 'sub1' )
  29.620 +        sub2 = sub1._getOb( 'sub2' )
  29.621 +
  29.622 +        self.assertEqual( len( sub2.objectIds() ), 1 )
  29.623 +        self.failUnless( FILENAME in sub2.objectIds() )
  29.624 +
  29.625 +        template = sub2._getOb( FILENAME )
  29.626 +
  29.627 +        self.assertEqual( template.getId(), FILENAME )
  29.628 +        self.assertEqual( template.meta_type, ZopePageTemplate.meta_type )
  29.629 +        self.assertEqual( template.read(), _HTML )
  29.630 +        self.failUnless( template.html() )
  29.631 +
  29.632 +    def test_writeDataFile_multiple( self ):
  29.633 +
  29.634 +        from string import printable
  29.635 +        from string import digits
  29.636 +
  29.637 +        site = DummySite( 'site' ).__of__( self.root )
  29.638 +        site.portal_setup = DummyTool( 'portal_setup' )
  29.639 +        tool = site.portal_setup
  29.640 +        ctx = self._makeOne( tool, 'multiple' )
  29.641 +
  29.642 +        ctx.writeDataFile( 'foo.txt', printable, 'text/plain' )
  29.643 +        ctx.writeDataFile( 'bar.txt', digits, 'text/plain' )
  29.644 +
  29.645 +        snapshot = tool.snapshots._getOb( 'multiple' )
  29.646 +
  29.647 +        self.assertEqual( len( snapshot.objectIds() ), 2 )
  29.648 +
  29.649 +        for id in [ 'foo.txt', 'bar.txt' ]:
  29.650 +            self.failUnless( id in snapshot.objectIds() )
  29.651 +
  29.652 +
  29.653 +class SnapshotImportContextTests( SecurityRequestTest
  29.654 +                                , ConformsToISetupContext
  29.655 +                                , ConformsToIImportContext
  29.656 +                                ):
  29.657 +
  29.658 +    def _getTargetClass( self ):
  29.659 +
  29.660 +        from Products.CMFSetup.context import SnapshotImportContext
  29.661 +        return SnapshotImportContext
  29.662 +
  29.663 +    def _makeOne( self, context_id, *args, **kw ):
  29.664 +
  29.665 +        site = DummySite( 'site' ).__of__( self.root )
  29.666 +        site._setObject( 'portal_setup', Folder( 'portal_setup' ) )
  29.667 +        tool = site._getOb( 'portal_setup' )
  29.668 +
  29.669 +        tool._setObject( 'snapshots', Folder( 'snapshots' ) )
  29.670 +        tool.snapshots._setObject( context_id, Folder( context_id ) )
  29.671 +
  29.672 +        ctx = self._getTargetClass()( tool, context_id, *args, **kw )
  29.673 +
  29.674 +        return site, tool, ctx.__of__( tool )
  29.675 +
  29.676 +    def _makeFile( self
  29.677 +                 , tool
  29.678 +                 , snapshot_id
  29.679 +                 , filename
  29.680 +                 , contents
  29.681 +                 , content_type='text/plain'
  29.682 +                 , mod_time=None
  29.683 +                 , subdir=None
  29.684 +                 ):
  29.685 +
  29.686 +        snapshots = tool._getOb( 'snapshots' )
  29.687 +        folder = snapshot = snapshots._getOb( snapshot_id )
  29.688 +
  29.689 +        if subdir is not None:
  29.690 +
  29.691 +            for element in subdir.split( '/' ):
  29.692 +
  29.693 +                try:
  29.694 +                    folder = folder._getOb( element )
  29.695 +                except AttributeError:
  29.696 +                    folder._setObject( element, Folder( element ) )
  29.697 +                    folder = folder._getOb( element )
  29.698 +
  29.699 +        file = File( filename, '', contents, content_type )
  29.700 +        folder._setObject( filename, file )
  29.701 +
  29.702 +        if mod_time is not None:
  29.703 +
  29.704 +            def __faux_mod_time():
  29.705 +                return mod_time
  29.706 +
  29.707 +            folder.bobobase_modification_time = \
  29.708 +            file.bobobase_modification_time = __faux_mod_time
  29.709 +
  29.710 +        return folder._getOb( filename )
  29.711 +
  29.712 +    def test_ctorparms( self ):
  29.713 +
  29.714 +        SNAPSHOT_ID = 'ctorparms'
  29.715 +        ENCODING = 'latin-1'
  29.716 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID
  29.717 +                                       , encoding=ENCODING
  29.718 +                                       , should_purge=True
  29.719 +                                       )
  29.720 +
  29.721 +        self.assertEqual( ctx.getEncoding(), ENCODING )
  29.722 +        self.assertEqual( ctx.shouldPurge(), True )
  29.723 +
  29.724 +    def test_empty( self ):
  29.725 +
  29.726 +        SNAPSHOT_ID = 'empty'
  29.727 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.728 +
  29.729 +        self.assertEqual( ctx.getSite(), site )
  29.730 +        self.assertEqual( ctx.getEncoding(), None )
  29.731 +        self.assertEqual( ctx.shouldPurge(), False )
  29.732 +
  29.733 +        # These methods are all specified to return 'None' for non-existing
  29.734 +        # paths / entities
  29.735 +        self.assertEqual( ctx.isDirectory( 'nonesuch/path' ), None )
  29.736 +        self.assertEqual( ctx.listDirectory( 'nonesuch/path' ), None )
  29.737 +
  29.738 +    def test_readDataFile_nonesuch( self ):
  29.739 +
  29.740 +        SNAPSHOT_ID = 'readDataFile_nonesuch'
  29.741 +        FILENAME = 'nonesuch.txt'
  29.742 +
  29.743 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.744 +
  29.745 +        self.assertEqual( ctx.readDataFile( FILENAME ), None )
  29.746 +        self.assertEqual( ctx.readDataFile( FILENAME, 'subdir' ), None )
  29.747 +
  29.748 +    def test_readDataFile_simple( self ):
  29.749 +
  29.750 +        from string import printable
  29.751 +
  29.752 +        SNAPSHOT_ID = 'readDataFile_simple'
  29.753 +        FILENAME = 'simple.txt'
  29.754 +
  29.755 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.756 +        self._makeFile( tool, SNAPSHOT_ID, FILENAME, printable )
  29.757 +
  29.758 +        self.assertEqual( ctx.readDataFile( FILENAME ), printable )
  29.759 +
  29.760 +    def test_readDataFile_subdir( self ):
  29.761 +
  29.762 +        from string import printable
  29.763 +
  29.764 +        SNAPSHOT_ID = 'readDataFile_subdir'
  29.765 +        FILENAME = 'subdir.txt'
  29.766 +        SUBDIR = 'subdir'
  29.767 +
  29.768 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.769 +        self._makeFile( tool, SNAPSHOT_ID, FILENAME, printable
  29.770 +                      , subdir=SUBDIR )
  29.771 +
  29.772 +        self.assertEqual( ctx.readDataFile( FILENAME, SUBDIR ), printable )
  29.773 +
  29.774 +    def test_getLastModified_nonesuch( self ):
  29.775 +
  29.776 +        SNAPSHOT_ID = 'getLastModified_nonesuch'
  29.777 +        FILENAME = 'nonesuch.txt'
  29.778 +
  29.779 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.780 +
  29.781 +        self.assertEqual( ctx.getLastModified( FILENAME ), None )
  29.782 +
  29.783 +    def test_getLastModified_simple( self ):
  29.784 +
  29.785 +        from string import printable
  29.786 +
  29.787 +        SNAPSHOT_ID = 'getLastModified_simple'
  29.788 +        FILENAME = 'simple.txt'
  29.789 +        WHEN = DateTime( '2004-01-01T00:00:00Z' )
  29.790 +
  29.791 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.792 +        file = self._makeFile( tool, SNAPSHOT_ID, FILENAME, printable
  29.793 +                             , mod_time=WHEN )
  29.794 +
  29.795 +        self.assertEqual( ctx.getLastModified( FILENAME ), WHEN )
  29.796 +
  29.797 +    def test_getLastModified_subdir( self ):
  29.798 +
  29.799 +        from string import printable
  29.800 +
  29.801 +        SNAPSHOT_ID = 'getLastModified_subdir'
  29.802 +        FILENAME = 'subdir.txt'
  29.803 +        SUBDIR = 'subdir'
  29.804 +        PATH = '%s/%s' % ( SUBDIR, FILENAME )
  29.805 +        WHEN = DateTime( '2004-01-01T00:00:00Z' )
  29.806 +
  29.807 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.808 +        file = self._makeFile( tool, SNAPSHOT_ID, FILENAME, printable
  29.809 +                             , mod_time=WHEN, subdir=SUBDIR )
  29.810 +
  29.811 +        self.assertEqual( ctx.getLastModified( PATH ), WHEN )
  29.812 +
  29.813 +    def test_getLastModified_directory( self ):
  29.814 +
  29.815 +        from string import printable
  29.816 +
  29.817 +        SNAPSHOT_ID = 'readDataFile_subdir'
  29.818 +        FILENAME = 'subdir.txt'
  29.819 +        SUBDIR = 'subdir'
  29.820 +        WHEN = DateTime( '2004-01-01T00:00:00Z' )
  29.821 +
  29.822 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.823 +        file = self._makeFile( tool, SNAPSHOT_ID, FILENAME, printable
  29.824 +                             , mod_time=WHEN, subdir=SUBDIR )
  29.825 +
  29.826 +        self.assertEqual( ctx.getLastModified( SUBDIR ), WHEN )
  29.827 +
  29.828 +    def test_isDirectory_nonesuch( self ):
  29.829 +
  29.830 +        SNAPSHOT_ID = 'isDirectory_nonesuch'
  29.831 +        FILENAME = 'nonesuch.txt'
  29.832 +
  29.833 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.834 +
  29.835 +        self.assertEqual( ctx.isDirectory( FILENAME ), None )
  29.836 +
  29.837 +    def test_isDirectory_simple( self ):
  29.838 +
  29.839 +        from string import printable
  29.840 +
  29.841 +        SNAPSHOT_ID = 'isDirectory_simple'
  29.842 +        FILENAME = 'simple.txt'
  29.843 +
  29.844 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.845 +        file = self._makeFile( tool, SNAPSHOT_ID, FILENAME, printable )
  29.846 +
  29.847 +        self.assertEqual( ctx.isDirectory( FILENAME ), False )
  29.848 +
  29.849 +    def test_isDirectory_nested( self ):
  29.850 +
  29.851 +        from string import printable
  29.852 +
  29.853 +        SNAPSHOT_ID = 'isDirectory_nested'
  29.854 +        SUBDIR = 'subdir'
  29.855 +        FILENAME = 'nested.txt'
  29.856 +        PATH = '%s/%s' % ( SUBDIR, FILENAME )
  29.857 +
  29.858 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.859 +        file = self._makeFile( tool, SNAPSHOT_ID, FILENAME, printable
  29.860 +                             , subdir=SUBDIR )
  29.861 +
  29.862 +        self.assertEqual( ctx.isDirectory( PATH ), False )
  29.863 +
  29.864 +    def test_isDirectory_subdir( self ):
  29.865 +
  29.866 +        from string import printable
  29.867 +
  29.868 +        SNAPSHOT_ID = 'isDirectory_subdir'
  29.869 +        SUBDIR = 'subdir'
  29.870 +        FILENAME = 'nested.txt'
  29.871 +        PATH = '%s/%s' % ( SUBDIR, FILENAME )
  29.872 +
  29.873 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.874 +        file = self._makeFile( tool, SNAPSHOT_ID, FILENAME, printable
  29.875 +                             , subdir=SUBDIR )
  29.876 +
  29.877 +        self.assertEqual( ctx.isDirectory( SUBDIR ), True )
  29.878 +
  29.879 +    def test_listDirectory_nonesuch( self ):
  29.880 +
  29.881 +        SNAPSHOT_ID = 'listDirectory_nonesuch'
  29.882 +        SUBDIR = 'nonesuch/path'
  29.883 +
  29.884 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.885 +
  29.886 +        self.assertEqual( ctx.listDirectory( SUBDIR ), None )
  29.887 +
  29.888 +    def test_listDirectory_root( self ):
  29.889 +
  29.890 +        from string import printable
  29.891 +
  29.892 +        SNAPSHOT_ID = 'listDirectory_root'
  29.893 +        FILENAME = 'simple.txt'
  29.894 +
  29.895 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.896 +        file = self._makeFile( tool, SNAPSHOT_ID, FILENAME, printable )
  29.897 +
  29.898 +        self.assertEqual( len( ctx.listDirectory( None ) ), 1 )
  29.899 +        self.failUnless( FILENAME in ctx.listDirectory( None ) )
  29.900 +
  29.901 +    def test_listDirectory_simple( self ):
  29.902 +
  29.903 +        from string import printable
  29.904 +
  29.905 +        SNAPSHOT_ID = 'listDirectory_simple'
  29.906 +        FILENAME = 'simple.txt'
  29.907 +
  29.908 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.909 +        file = self._makeFile( tool, SNAPSHOT_ID, FILENAME, printable )
  29.910 +
  29.911 +        self.assertEqual( ctx.listDirectory( FILENAME ), None )
  29.912 +
  29.913 +    def test_listDirectory_nested( self ):
  29.914 +
  29.915 +        from string import printable
  29.916 +
  29.917 +        SNAPSHOT_ID = 'listDirectory_nested'
  29.918 +        SUBDIR = 'subdir'
  29.919 +        FILENAME = 'nested.txt'
  29.920 +        PATH = '%s/%s' % ( SUBDIR, FILENAME )
  29.921 +
  29.922 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.923 +        file = self._makeFile( tool, SNAPSHOT_ID, FILENAME, printable
  29.924 +                             , subdir=SUBDIR )
  29.925 +
  29.926 +        self.assertEqual( ctx.listDirectory( PATH ), None )
  29.927 +
  29.928 +    def test_listDirectory_single( self ):
  29.929 +
  29.930 +        from string import printable
  29.931 +
  29.932 +        SNAPSHOT_ID = 'listDirectory_nested'
  29.933 +        SUBDIR = 'subdir'
  29.934 +        FILENAME = 'nested.txt'
  29.935 +
  29.936 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.937 +        file = self._makeFile( tool, SNAPSHOT_ID, FILENAME, printable
  29.938 +                             , subdir=SUBDIR )
  29.939 +
  29.940 +        names = ctx.listDirectory( SUBDIR )
  29.941 +        self.assertEqual( len( names ), 1 )
  29.942 +        self.failUnless( FILENAME in names )
  29.943 +
  29.944 +    def test_listDirectory_multiple( self ):
  29.945 +
  29.946 +        from string import printable, uppercase
  29.947 +
  29.948 +        SNAPSHOT_ID = 'listDirectory_nested'
  29.949 +        SUBDIR = 'subdir'
  29.950 +        FILENAME1 = 'nested.txt'
  29.951 +        FILENAME2 = 'another.txt'
  29.952 +
  29.953 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.954 +        file1 = self._makeFile( tool, SNAPSHOT_ID, FILENAME1, printable
  29.955 +                              , subdir=SUBDIR )
  29.956 +        file2 = self._makeFile( tool, SNAPSHOT_ID, FILENAME2, uppercase
  29.957 +                              , subdir=SUBDIR )
  29.958 +
  29.959 +        names = ctx.listDirectory( SUBDIR )
  29.960 +        self.assertEqual( len( names ), 2 )
  29.961 +        self.failUnless( FILENAME1 in names )
  29.962 +        self.failUnless( FILENAME2 in names )
  29.963 +
  29.964 +    def test_listDirectory_skip( self ):
  29.965 +
  29.966 +        from string import printable, uppercase
  29.967 +
  29.968 +        SNAPSHOT_ID = 'listDirectory_nested'
  29.969 +        SUBDIR = 'subdir'
  29.970 +        FILENAME1 = 'nested.txt'
  29.971 +        FILENAME2 = 'another.txt'
  29.972 +
  29.973 +        site, tool, ctx = self._makeOne( SNAPSHOT_ID )
  29.974 +        file1 = self._makeFile( tool, SNAPSHOT_ID, FILENAME1, printable
  29.975 +                              , subdir=SUBDIR )
  29.976 +        file2 = self._makeFile( tool, SNAPSHOT_ID, FILENAME2, uppercase
  29.977 +                              , subdir=SUBDIR )
  29.978 +
  29.979 +        names = ctx.listDirectory( SUBDIR, skip=( FILENAME1, ) )
  29.980 +        self.assertEqual( len( names ), 1 )
  29.981 +        self.failIf( FILENAME1 in names )
  29.982 +        self.failUnless( FILENAME2 in names )
  29.983 +
  29.984 +
  29.985 +def test_suite():
  29.986 +    return unittest.TestSuite((
  29.987 +        unittest.makeSuite( DirectoryImportContextTests ),
  29.988 +        unittest.makeSuite( DirectoryExportContextTests ),
  29.989 +        unittest.makeSuite( TarballExportContextTests ),
  29.990 +        unittest.makeSuite( SnapshotExportContextTests ),
  29.991 +        unittest.makeSuite( SnapshotImportContextTests ),
  29.992 +        ))
  29.993 +
  29.994 +if __name__ == '__main__':
  29.995 +    unittest.main(defaultTest='test_suite')
    30.1 new file mode 100644
    30.2 --- /dev/null
    30.3 +++ b/tests/test_differ.py
    30.4 @@ -0,0 +1,409 @@
    30.5 +##############################################################################
    30.6 +#
    30.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    30.8 +#
    30.9 +# This software is subject to the provisions of the Zope Public License,
   30.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   30.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   30.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   30.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   30.14 +# FOR A PARTICULAR PURPOSE.
   30.15 +#
   30.16 +##############################################################################
   30.17 +""" Unit tests for differ module.
   30.18 +
   30.19 +$Id: test_differ.py 37135 2005-07-08 13:24:33Z tseaver $
   30.20 +"""
   30.21 +
   30.22 +import unittest
   30.23 +import Testing
   30.24 +try:
   30.25 +    import Zope2
   30.26 +except ImportError: # BBB: for Zope 2.7
   30.27 +    import Zope as Zope2
   30.28 +Zope2.startup()
   30.29 +
   30.30 +from OFS.Folder import Folder
   30.31 +from OFS.Image import File
   30.32 +
   30.33 +from DateTime.DateTime import DateTime
   30.34 +from Products.CMFCore.tests.base.testcase import SecurityRequestTest
   30.35 +
   30.36 +
   30.37 +class DummySite( Folder ):
   30.38 +
   30.39 +    pass
   30.40 +
   30.41 +
   30.42 +class Test_unidiff( unittest.TestCase ):
   30.43 +
   30.44 +    def test_unidiff_both_text( self ):
   30.45 +
   30.46 +        from Products.CMFSetup.differ import unidiff
   30.47 +
   30.48 +        diff_lines = unidiff( ONE_FOUR, ZERO_FOUR )
   30.49 +        diff_text = '\n'.join( diff_lines )
   30.50 +        self.assertEqual( diff_text, DIFF_TEXT )
   30.51 +
   30.52 +    def test_unidiff_both_lines( self ):
   30.53 +
   30.54 +        from Products.CMFSetup.differ import unidiff
   30.55 +
   30.56 +        diff_lines = unidiff( ONE_FOUR.splitlines(), ZERO_FOUR.splitlines() )
   30.57 +        diff_text = '\n'.join( diff_lines )
   30.58 +        self.assertEqual( diff_text, DIFF_TEXT )
   30.59 +
   30.60 +    def test_unidiff_mixed( self ):
   30.61 +
   30.62 +        from Products.CMFSetup.differ import unidiff
   30.63 +
   30.64 +        diff_lines = unidiff( ONE_FOUR, ZERO_FOUR.splitlines() )
   30.65 +        diff_text = '\n'.join( diff_lines )
   30.66 +        self.assertEqual( diff_text, DIFF_TEXT )
   30.67 +
   30.68 +    def test_unidiff_ignore_blanks( self ):
   30.69 +
   30.70 +        from Products.CMFSetup.differ import unidiff
   30.71 +
   30.72 +        double_spaced = ONE_FOUR.replace( '\n', '\n\n' )
   30.73 +        diff_lines = unidiff( double_spaced
   30.74 +                            , ZERO_FOUR.splitlines()
   30.75 +                            , ignore_blanks=True
   30.76 +                            )
   30.77 +
   30.78 +        diff_text = '\n'.join( diff_lines )
   30.79 +        self.assertEqual( diff_text, DIFF_TEXT )
   30.80 +
   30.81 +ZERO_FOUR = """\
   30.82 +zero
   30.83 +one
   30.84 +tree
   30.85 +four
   30.86 +"""
   30.87 +
   30.88 +ONE_FOUR = """\
   30.89 +one
   30.90 +two
   30.91 +three
   30.92 +four
   30.93 +"""
   30.94 +
   30.95 +DIFF_TEXT = """\
   30.96 +--- original None
   30.97 ++++ modified None
   30.98 +@@ -1,4 +1,4 @@
   30.99 ++zero
  30.100 + one
  30.101 +-two
  30.102 +-three
  30.103 ++tree
  30.104 + four\
  30.105 +"""
  30.106 +
  30.107 +class ConfigDiffTests( SecurityRequestTest ):
  30.108 +
  30.109 +    site = None
  30.110 +    tool = None
  30.111 +
  30.112 +    def _getTargetClass( self ):
  30.113 +
  30.114 +        from Products.CMFSetup.differ import ConfigDiff
  30.115 +        return ConfigDiff
  30.116 +
  30.117 +    def _makeOne( self, lhs, rhs, *args, **kw ):
  30.118 +
  30.119 +        return self._getTargetClass()( lhs, rhs, *args, **kw )
  30.120 +
  30.121 +    def _makeSite( self ):
  30.122 +
  30.123 +        if self.site is not None:
  30.124 +            return
  30.125 +
  30.126 +        site = self.site = DummySite( 'site' ).__of__( self.root )
  30.127 +        site._setObject( 'portal_setup', Folder( 'portal_setup' ) )
  30.128 +        self.tool = tool = site._getOb( 'portal_setup' )
  30.129 +
  30.130 +        tool._setObject( 'snapshots', Folder( 'snapshots' ) )
  30.131 +
  30.132 +    def _makeContext( self, context_id ):
  30.133 +
  30.134 +        from Products.CMFSetup.context import SnapshotImportContext
  30.135 +
  30.136 +        self._makeSite()
  30.137 +
  30.138 +        if context_id not in self.tool.snapshots.objectIds():
  30.139 +            self.tool.snapshots._setObject( context_id, Folder( context_id ) )
  30.140 +
  30.141 +        ctx = SnapshotImportContext( self.tool, context_id )
  30.142 +
  30.143 +        return ctx.__of__( self.tool )
  30.144 +
  30.145 +    def _makeDirectory( self, snapshot_id, subdir ):
  30.146 +
  30.147 +        self._makeSite()
  30.148 +        folder = self.tool.snapshots._getOb( snapshot_id )
  30.149 +
  30.150 +        for element in subdir.split( '/' ):
  30.151 +
  30.152 +            try:
  30.153 +                folder = folder._getOb( element )
  30.154 +            except AttributeError:
  30.155 +                folder._setObject( element, Folder( element ) )
  30.156 +                folder = folder._getOb( element )
  30.157 +
  30.158 +        return folder
  30.159 +
  30.160 +    def _makeFile( self
  30.161 +                 , snapshot_id
  30.162 +                 , filename
  30.163 +                 , contents
  30.164 +                 , content_type='text/plain'
  30.165 +                 , mod_time=None
  30.166 +                 , subdir=None
  30.167 +                 ):
  30.168 +
  30.169 +        self._makeSite()
  30.170 +        snapshots = self.tool.snapshots
  30.171 +        snapshot = snapshots._getOb( snapshot_id )
  30.172 +
  30.173 +        if subdir is not None:
  30.174 +            folder = self._makeDirectory( snapshot_id, subdir )
  30.175 +        else:
  30.176 +            folder = snapshot
  30.177 +
  30.178 +        file = File( filename, '', contents, content_type )
  30.179 +        folder._setObject( filename, file )
  30.180 +
  30.181 +        if mod_time is not None:
  30.182 +
  30.183 +            def __faux_mod_time():
  30.184 +                return mod_time
  30.185 +
  30.186 +            folder.bobobase_modification_time = \
  30.187 +            file.bobobase_modification_time = __faux_mod_time
  30.188 +
  30.189 +        return folder._getOb( filename )
  30.190 +
  30.191 +    def test_compare_empties( self ):
  30.192 +
  30.193 +        lhs = self._makeContext( 'lhs' )
  30.194 +        rhs = self._makeContext( 'rhs' )
  30.195 +
  30.196 +        cd = self._makeOne( lhs, rhs )
  30.197 +
  30.198 +        diffs = cd.compare()
  30.199 +
  30.200 +        self.assertEqual( diffs, '' )
  30.201 +
  30.202 +    def test_compare_identical( self ):
  30.203 +
  30.204 +        lhs = self._makeContext( 'lhs' )
  30.205 +        rhs = self._makeContext( 'rhs' )
  30.206 +
  30.207 +        self._makeFile( 'lhs', 'test.txt', 'ABCDEF' )
  30.208 +        self._makeFile( 'lhs', 'again.txt', 'GHIJKL', subdir='sub' )
  30.209 +        self._makeFile( 'rhs', 'test.txt', 'ABCDEF' )
  30.210 +        self._makeFile( 'rhs', 'again.txt', 'GHIJKL', subdir='sub' )
  30.211 +
  30.212 +        cd = self._makeOne( lhs, rhs )
  30.213 +
  30.214 +        diffs = cd.compare()
  30.215 +
  30.216 +        self.assertEqual( diffs, '' )
  30.217 +
  30.218 +    def test_compare_changed_file( self ):
  30.219 +
  30.220 +        BEFORE = DateTime( '2004-01-01T00:00:00Z' )
  30.221 +        AFTER = DateTime( '2004-02-29T23:59:59Z' )
  30.222 +
  30.223 +        lhs = self._makeContext( 'lhs' )
  30.224 +        rhs = self._makeContext( 'rhs' )
  30.225 +
  30.226 +        self._makeFile( 'lhs', 'test.txt', 'ABCDEF\nWXYZ', mod_time=BEFORE )
  30.227 +        self._makeFile( 'lhs', 'again.txt', 'GHIJKL', subdir='sub' )
  30.228 +        self._makeFile( 'rhs', 'test.txt', 'ABCDEF\nQRST', mod_time=AFTER )
  30.229 +        self._makeFile( 'rhs', 'again.txt', 'GHIJKL', subdir='sub' )
  30.230 +
  30.231 +        cd = self._makeOne( lhs, rhs )
  30.232 +
  30.233 +        diffs = cd.compare()
  30.234 +
  30.235 +        self.assertEqual( diffs, TEST_TXT_DIFFS % ( BEFORE, AFTER ) )
  30.236 +
  30.237 +    def test_compare_changed_file_ignore_blanks( self ):
  30.238 +
  30.239 +        BEFORE = DateTime( '2004-01-01T00:00:00Z' )
  30.240 +        AFTER = DateTime( '2004-02-29T23:59:59Z' )
  30.241 +
  30.242 +        lhs = self._makeContext( 'lhs' )
  30.243 +        rhs = self._makeContext( 'rhs' )
  30.244 +
  30.245 +        self._makeFile( 'lhs', 'test.txt', 'ABCDEF\nWXYZ', mod_time=BEFORE )
  30.246 +        self._makeFile( 'rhs', 'test.txt', 'ABCDEF\n\n\nWXYZ', mod_time=AFTER )
  30.247 +
  30.248 +        cd = self._makeOne( lhs, rhs, ignore_blanks=True )
  30.249 +
  30.250 +        diffs = cd.compare()
  30.251 +
  30.252 +        self.assertEqual( diffs, '' )
  30.253 +
  30.254 +    def test_compare_changed_file_explicit_skip( self ):
  30.255 +
  30.256 +        BEFORE = DateTime( '2004-01-01T00:00:00Z' )
  30.257 +        AFTER = DateTime( '2004-02-29T23:59:59Z' )
  30.258 +
  30.259 +        lhs = self._makeContext( 'lhs' )
  30.260 +        rhs = self._makeContext( 'rhs' )
  30.261 +
  30.262 +        self._makeFile( 'lhs', 'test.txt', 'ABCDEF\nWXYZ', subdir='skipme'
  30.263 +                      , mod_time=BEFORE )
  30.264 +        self._makeFile( 'lhs', 'again.txt', 'GHIJKL', subdir='sub' )
  30.265 +        self._makeFile( 'rhs', 'test.txt', 'ABCDEF\nQRST', subdir='skipme'
  30.266 +                      , mod_time=AFTER )
  30.267 +        self._makeFile( 'rhs', 'again.txt', 'GHIJKL', subdir='sub' )
  30.268 +
  30.269 +        cd = self._makeOne( lhs, rhs, skip=[ 'skipme' ] )
  30.270 +
  30.271 +        diffs = cd.compare()
  30.272 +
  30.273 +        self.assertEqual( diffs, '' )
  30.274 +
  30.275 +    def test_compare_changed_file_implicit_skip( self ):
  30.276 +
  30.277 +        BEFORE = DateTime( '2004-01-01T00:00:00Z' )
  30.278 +        AFTER = DateTime( '2004-02-29T23:59:59Z' )
  30.279 +
  30.280 +        lhs = self._makeContext( 'lhs' )
  30.281 +        rhs = self._makeContext( 'rhs' )
  30.282 +
  30.283 +        self._makeFile( 'lhs', 'test.txt', 'ABCDEF\nWXYZ', subdir='CVS'
  30.284 +                      , mod_time=BEFORE )
  30.285 +        self._makeFile( 'lhs', 'again.txt', 'GHIJKL', subdir='.svn'
  30.286 +                      , mod_time=BEFORE )
  30.287 +
  30.288 +        self._makeFile( 'rhs', 'test.txt', 'ABCDEF\nQRST', subdir='CVS'
  30.289 +                      , mod_time=AFTER )
  30.290 +        self._makeFile( 'rhs', 'again.txt', 'MNOPQR', subdir='.svn'
  30.291 +                      , mod_time=AFTER )
  30.292 +
  30.293 +        cd = self._makeOne( lhs, rhs )
  30.294 +
  30.295 +        diffs = cd.compare()
  30.296 +
  30.297 +        self.assertEqual( diffs, '' )
  30.298 +
  30.299 +    def test_compare_added_file_no_missing_as_empty( self ):
  30.300 +
  30.301 +        lhs = self._makeContext( 'lhs' )
  30.302 +        rhs = self._makeContext( 'rhs' )
  30.303 +
  30.304 +        self._makeFile( 'lhs', 'test.txt', 'ABCDEF\nWXYZ' )
  30.305 +        self._makeDirectory( 'lhs', subdir='sub' )
  30.306 +        self._makeFile( 'rhs', 'test.txt', 'ABCDEF\nWXYZ' )
  30.307 +        self._makeFile( 'rhs', 'again.txt', 'GHIJKL', subdir='sub' )
  30.308 +
  30.309 +        cd = self._makeOne( lhs, rhs )
  30.310 +
  30.311 +        diffs = cd.compare()
  30.312 +
  30.313 +        self.assertEqual( diffs, ADDED_FILE_DIFFS_NO_MAE )
  30.314 +
  30.315 +    def test_compare_added_file_missing_as_empty( self ):
  30.316 +
  30.317 +        AFTER = DateTime( '2004-02-29T23:59:59Z' )
  30.318 +        lhs = self._makeContext( 'lhs' )
  30.319 +        rhs = self._makeContext( 'rhs' )
  30.320 +
  30.321 +        self._makeFile( 'lhs', 'test.txt', 'ABCDEF\nWXYZ' )
  30.322 +        self._makeDirectory( 'lhs', subdir='sub' )
  30.323 +        self._makeFile( 'rhs', 'test.txt', 'ABCDEF\nWXYZ' )
  30.324 +        self._makeFile( 'rhs', 'again.txt', 'GHIJKL', subdir='sub'
  30.325 +                      , mod_time=AFTER )
  30.326 +
  30.327 +        cd = self._makeOne( lhs, rhs, missing_as_empty=True )
  30.328 +
  30.329 +        diffs = cd.compare()
  30.330 +
  30.331 +        self.assertEqual( diffs, ADDED_FILE_DIFFS_MAE % AFTER )
  30.332 +
  30.333 +    def test_compare_removed_file_no_missing_as_empty( self ):
  30.334 +
  30.335 +        lhs = self._makeContext( 'lhs' )
  30.336 +        rhs = self._makeContext( 'rhs' )
  30.337 +
  30.338 +        self._makeFile( 'lhs', 'test.txt', 'ABCDEF\nWXYZ' )
  30.339 +        self._makeFile( 'lhs', 'again.txt', 'GHIJKL', subdir='sub' )
  30.340 +        self._makeFile( 'rhs', 'test.txt', 'ABCDEF\nWXYZ' )
  30.341 +        self._makeDirectory( 'rhs', subdir='sub' )
  30.342 +
  30.343 +        cd = self._makeOne( lhs, rhs )
  30.344 +
  30.345 +        diffs = cd.compare()
  30.346 +
  30.347 +        self.assertEqual( diffs, REMOVED_FILE_DIFFS_NO_MAE )
  30.348 +
  30.349 +    def test_compare_removed_file_missing_as_empty( self ):
  30.350 +
  30.351 +        BEFORE = DateTime( '2004-01-01T00:00:00Z' )
  30.352 +        lhs = self._makeContext( 'lhs' )
  30.353 +        rhs = self._makeContext( 'rhs' )
  30.354 +
  30.355 +        self._makeFile( 'lhs', 'test.txt', 'ABCDEF\nWXYZ' )
  30.356 +        self._makeFile( 'lhs', 'again.txt', 'GHIJKL', subdir='sub'
  30.357 +                      , mod_time=BEFORE )
  30.358 +        self._makeFile( 'rhs', 'test.txt', 'ABCDEF\nWXYZ' )
  30.359 +        self._makeDirectory( 'rhs', subdir='sub' )
  30.360 +
  30.361 +        cd = self._makeOne( lhs, rhs, missing_as_empty=True )
  30.362 +
  30.363 +        diffs = cd.compare()
  30.364 +
  30.365 +        self.assertEqual( diffs, REMOVED_FILE_DIFFS_MAE % BEFORE )
  30.366 +
  30.367 +
  30.368 +TEST_TXT_DIFFS = """\
  30.369 +Index: test.txt
  30.370 +===================================================================
  30.371 +--- test.txt %s
  30.372 ++++ test.txt %s
  30.373 +@@ -1,2 +1,2 @@
  30.374 + ABCDEF
  30.375 +-WXYZ
  30.376 ++QRST\
  30.377 +"""
  30.378 +
  30.379 +ADDED_FILE_DIFFS_NO_MAE = """\
  30.380 +** File sub/again.txt added
  30.381 +"""
  30.382 +
  30.383 +ADDED_FILE_DIFFS_MAE = """\
  30.384 +Index: sub/again.txt
  30.385 +===================================================================
  30.386 +--- sub/again.txt 0
  30.387 ++++ sub/again.txt %s
  30.388 +@@ -1,0 +1,1 @@
  30.389 ++GHIJKL\
  30.390 +"""
  30.391 +
  30.392 +REMOVED_FILE_DIFFS_NO_MAE = """\
  30.393 +** File sub/again.txt removed
  30.394 +"""
  30.395 +
  30.396 +REMOVED_FILE_DIFFS_MAE = """\
  30.397 +Index: sub/again.txt
  30.398 +===================================================================
  30.399 +--- sub/again.txt %s
  30.400 ++++ sub/again.txt 0
  30.401 +@@ -1,1 +1,0 @@
  30.402 +-GHIJKL\
  30.403 +"""
  30.404 +
  30.405 +
  30.406 +def test_suite():
  30.407 +    return unittest.TestSuite((
  30.408 +        unittest.makeSuite( Test_unidiff ),
  30.409 +        unittest.makeSuite( ConfigDiffTests ),
  30.410 +        ))
  30.411 +
  30.412 +if __name__ == '__main__':
  30.413 +    unittest.main(defaultTest='test_suite')
    31.1 new file mode 100644
    31.2 --- /dev/null
    31.3 +++ b/tests/test_properties.py
    31.4 @@ -0,0 +1,282 @@
    31.5 +##############################################################################
    31.6 +#
    31.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    31.8 +#
    31.9 +# This software is subject to the provisions of the Zope Public License,
   31.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   31.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   31.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   31.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   31.14 +# FOR A PARTICULAR PURPOSE.
   31.15 +#
   31.16 +##############################################################################
   31.17 +""" Site properties export / import unit tests.
   31.18 +
   31.19 +$Id: test_properties.py 37135 2005-07-08 13:24:33Z tseaver $
   31.20 +"""
   31.21 +
   31.22 +import unittest
   31.23 +import Testing
   31.24 +try:
   31.25 +    import Zope2
   31.26 +except ImportError: # BBB: for Zope 2.7
   31.27 +    import Zope as Zope2
   31.28 +Zope2.startup()
   31.29 +
   31.30 +from OFS.Folder import Folder
   31.31 +
   31.32 +from common import BaseRegistryTests
   31.33 +from common import DummyExportContext
   31.34 +from common import DummyImportContext
   31.35 +
   31.36 +
   31.37 +_EMPTY_EXPORT = """\
   31.38 +<?xml version="1.0"?>
   31.39 +<site>
   31.40 +</site>
   31.41 +"""
   31.42 +
   31.43 +_NORMAL_EXPORT = """\
   31.44 +<?xml version="1.0"?>
   31.45 +<site>
   31.46 +  <property name="foo" type="string">Foo</property>
   31.47 +  <property name="bar" type="tokens">
   31.48 +   <element value="Bar"/></property>
   31.49 +</site>
   31.50 +"""
   31.51 +
   31.52 +
   31.53 +class DummySite(Folder):
   31.54 +
   31.55 +    _properties = ()
   31.56 +
   31.57 +
   31.58 +class _SitePropertiesSetup(BaseRegistryTests):
   31.59 +
   31.60 +    def _initSite(self, foo=2, bar=2):
   31.61 +
   31.62 +        self.root.site = DummySite()
   31.63 +        site = self.root.site
   31.64 +
   31.65 +        if foo > 0:
   31.66 +            site._setProperty('foo', '', 'string')
   31.67 +        if foo > 1:
   31.68 +            site._updateProperty('foo', 'Foo')
   31.69 +
   31.70 +        if bar > 0:
   31.71 +            site._setProperty( 'bar', (), 'tokens' )
   31.72 +        if bar > 1:
   31.73 +            site._updateProperty( 'bar', ('Bar',) )
   31.74 +
   31.75 +        return site
   31.76 +
   31.77 +
   31.78 +class SitePropertiesConfiguratorTests(_SitePropertiesSetup):
   31.79 +
   31.80 +    def _getTargetClass(self):
   31.81 +
   31.82 +        from Products.CMFSetup.properties import SitePropertiesConfigurator
   31.83 +        return SitePropertiesConfigurator
   31.84 +
   31.85 +    def test_listSiteInfos_normal(self):
   31.86 +
   31.87 +        site = self._initSite()
   31.88 +
   31.89 +        EXPECTED = [ { 'id': 'foo',
   31.90 +                       'value': 'Foo',
   31.91 +                       'elements': (),
   31.92 +                       'type': 'string',
   31.93 +                       'select_variable': None },
   31.94 +                     { 'id': 'bar',
   31.95 +                       'value': '',
   31.96 +                       'elements': ('Bar',),
   31.97 +                       'type': 'tokens',
   31.98 +                       'select_variable': None } ]
   31.99 +
  31.100 +        configurator = self._makeOne(site)
  31.101 +
  31.102 +        site_info = configurator.listSiteInfos()
  31.103 +        self.assertEqual( len(site_info), len(EXPECTED) )
  31.104 +
  31.105 +        for found, expected in zip(site_info, EXPECTED):
  31.106 +            self.assertEqual(found, expected)
  31.107 +
  31.108 +    def test_generateXML_empty(self):
  31.109 +
  31.110 +        site = self._initSite(0, 0)
  31.111 +        configurator = self._makeOne(site).__of__(site)
  31.112 +
  31.113 +        self._compareDOM(configurator.generateXML(), _EMPTY_EXPORT)
  31.114 +
  31.115 +    def test_generateXML_normal(self):
  31.116 +
  31.117 +        site = self._initSite()
  31.118 +        configurator = self._makeOne(site).__of__(site)
  31.119 +
  31.120 +        self._compareDOM( configurator.generateXML(), _NORMAL_EXPORT )
  31.121 +
  31.122 +    def test_parseXML_empty(self):
  31.123 +
  31.124 +        site = self._initSite(0, 0)
  31.125 +        configurator = self._makeOne(site)
  31.126 +        site_info = configurator.parseXML(_EMPTY_EXPORT)
  31.127 +
  31.128 +        self.assertEqual( len( site_info['properties'] ), 0 )
  31.129 +
  31.130 +    def test_parseXML_normal(self):
  31.131 +
  31.132 +        site = self._initSite()
  31.133 +        configurator = self._makeOne(site)
  31.134 +        site_info = configurator.parseXML(_NORMAL_EXPORT)
  31.135 +
  31.136 +        self.assertEqual( len( site_info['properties'] ), 2 )
  31.137 +
  31.138 +        info = site_info['properties'][0]
  31.139 +        self.assertEqual( info['id'], 'foo' )
  31.140 +        self.assertEqual( info['value'], 'Foo' )
  31.141 +        self.assertEqual( len( info['elements'] ), 0 )
  31.142 +
  31.143 +        info = site_info['properties'][1]
  31.144 +        self.assertEqual( info['id'], 'bar' )
  31.145 +        self.assertEqual( info['value'], '' )
  31.146 +        self.assertEqual( len( info['elements'] ), 1 )
  31.147 +        self.assertEqual( info['elements'][0], 'Bar' )
  31.148 +
  31.149 +
  31.150 +class Test_exportSiteProperties(_SitePropertiesSetup):
  31.151 +
  31.152 +    def test_empty(self):
  31.153 +
  31.154 +        site = self._initSite(0, 0)
  31.155 +        context = DummyExportContext(site)
  31.156 +
  31.157 +        from Products.CMFSetup.properties import exportSiteProperties
  31.158 +        exportSiteProperties(context)
  31.159 +
  31.160 +        self.assertEqual( len(context._wrote), 1 )
  31.161 +        filename, text, content_type = context._wrote[0]
  31.162 +        self.assertEqual(filename, 'properties.xml')
  31.163 +        self._compareDOM(text, _EMPTY_EXPORT)
  31.164 +        self.assertEqual(content_type, 'text/xml')
  31.165 +
  31.166 +    def test_normal(self):
  31.167 +
  31.168 +        site = self._initSite()
  31.169 +        context = DummyExportContext( site )
  31.170 +
  31.171 +        from Products.CMFSetup.properties import exportSiteProperties
  31.172 +        exportSiteProperties(context)
  31.173 +
  31.174 +        self.assertEqual( len(context._wrote), 1 )
  31.175 +        filename, text, content_type = context._wrote[0]
  31.176 +        self.assertEqual(filename, 'properties.xml')
  31.177 +        self._compareDOM(text, _NORMAL_EXPORT)
  31.178 +        self.assertEqual(content_type, 'text/xml')
  31.179 +
  31.180 +
  31.181 +class Test_importSiteProperties(_SitePropertiesSetup):
  31.182 +
  31.183 +    def test_empty_default_purge(self):
  31.184 +
  31.185 +        site = self._initSite()
  31.186 +
  31.187 +        self.assertEqual( len( site.propertyIds() ), 2 )
  31.188 +        self.failUnless( 'foo' in site.propertyIds() )
  31.189 +        self.assertEqual( site.getProperty('foo'), 'Foo' )
  31.190 +        self.failUnless( 'bar' in site.propertyIds() )
  31.191 +        self.assertEqual( site.getProperty('bar'), ('Bar',) )
  31.192 +
  31.193 +        context = DummyImportContext(site)
  31.194 +        context._files['properties.xml'] = _EMPTY_EXPORT
  31.195 +
  31.196 +        from Products.CMFSetup.properties import importSiteProperties
  31.197 +        importSiteProperties(context)
  31.198 +
  31.199 +        self.assertEqual( len( site.propertyIds() ), 0 )
  31.200 +
  31.201 +    def test_empty_explicit_purge(self):
  31.202 +
  31.203 +        site = self._initSite()
  31.204 +
  31.205 +        self.assertEqual( len( site.propertyIds() ), 2 )
  31.206 +        self.failUnless( 'foo' in site.propertyIds() )
  31.207 +        self.assertEqual( site.getProperty('foo'), 'Foo' )
  31.208 +        self.failUnless( 'bar' in site.propertyIds() )
  31.209 +        self.assertEqual( site.getProperty('bar'), ('Bar',) )
  31.210 +
  31.211 +        context = DummyImportContext(site, True)
  31.212 +        context._files['properties.xml'] = _EMPTY_EXPORT
  31.213 +
  31.214 +        from Products.CMFSetup.properties import importSiteProperties
  31.215 +        importSiteProperties(context)
  31.216 +
  31.217 +        self.assertEqual( len( site.propertyIds() ), 0 )
  31.218 +
  31.219 +    def test_empty_skip_purge(self):
  31.220 +
  31.221 +        site = self._initSite()
  31.222 +
  31.223 +        self.assertEqual( len( site.propertyIds() ), 2 )
  31.224 +        self.failUnless( 'foo' in site.propertyIds() )
  31.225 +        self.assertEqual( site.getProperty('foo'), 'Foo' )
  31.226 +        self.failUnless( 'bar' in site.propertyIds() )
  31.227 +        self.assertEqual( site.getProperty('bar'), ('Bar',) )
  31.228 +
  31.229 +        context = DummyImportContext(site, False)
  31.230 +        context._files['properties.xml'] = _EMPTY_EXPORT
  31.231 +
  31.232 +        from Products.CMFSetup.properties import importSiteProperties
  31.233 +        importSiteProperties(context)
  31.234 +
  31.235 +        self.assertEqual( len( site.propertyIds() ), 2 )
  31.236 +        self.failUnless( 'foo' in site.propertyIds() )
  31.237 +        self.assertEqual( site.getProperty('foo'), 'Foo' )
  31.238 +        self.failUnless( 'bar' in site.propertyIds() )
  31.239 +        self.assertEqual( site.getProperty('bar'), ('Bar',) )
  31.240 +
  31.241 +    def test_normal(self):
  31.242 +
  31.243 +        site = self._initSite(0,0)
  31.244 +
  31.245 +        self.assertEqual( len( site.propertyIds() ), 0 )
  31.246 +
  31.247 +        context = DummyImportContext(site)
  31.248 +        context._files['properties.xml'] = _NORMAL_EXPORT
  31.249 +
  31.250 +        from Products.CMFSetup.properties import importSiteProperties
  31.251 +        importSiteProperties(context)
  31.252 +
  31.253 +        self.assertEqual( len( site.propertyIds() ), 2 )
  31.254 +        self.failUnless( 'foo' in site.propertyIds() )
  31.255 +        self.assertEqual( site.getProperty('foo'), 'Foo' )
  31.256 +        self.failUnless( 'bar' in site.propertyIds() )
  31.257 +        self.assertEqual( site.getProperty('bar'), ('Bar',) )
  31.258 +
  31.259 +    def test_normal_encode_as_ascii(self):
  31.260 +
  31.261 +        site = self._initSite(0,0)
  31.262 +
  31.263 +        self.assertEqual( len( site.propertyIds() ), 0 )
  31.264 +
  31.265 +        context = DummyImportContext(site, encoding='ascii')
  31.266 +        context._files['properties.xml'] = _NORMAL_EXPORT
  31.267 +
  31.268 +        from Products.CMFSetup.properties import importSiteProperties
  31.269 +        importSiteProperties(context)
  31.270 +
  31.271 +        self.assertEqual( len( site.propertyIds() ), 2 )
  31.272 +        self.failUnless( 'foo' in site.propertyIds() )
  31.273 +        self.assertEqual( site.getProperty('foo'), 'Foo' )
  31.274 +        self.failUnless( 'bar' in site.propertyIds() )
  31.275 +        self.assertEqual( site.getProperty('bar'), ('Bar',) )
  31.276 +
  31.277 +
  31.278 +def test_suite():
  31.279 +    return unittest.TestSuite((
  31.280 +        unittest.makeSuite(SitePropertiesConfiguratorTests),
  31.281 +        unittest.makeSuite(Test_exportSiteProperties),
  31.282 +        unittest.makeSuite(Test_importSiteProperties),
  31.283 +        ))
  31.284 +
  31.285 +if __name__ == '__main__':
  31.286 +    unittest.main(defaultTest='test_suite')
    32.1 new file mode 100644
    32.2 --- /dev/null
    32.3 +++ b/tests/test_registry.py
    32.4 @@ -0,0 +1,1101 @@
    32.5 +##############################################################################
    32.6 +#
    32.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    32.8 +#
    32.9 +# This software is subject to the provisions of the Zope Public License,
   32.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   32.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   32.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   32.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   32.14 +# FOR A PARTICULAR PURPOSE.
   32.15 +#
   32.16 +##############################################################################
   32.17 +""" Registry unit tests.
   32.18 +
   32.19 +$Id: test_registry.py 37135 2005-07-08 13:24:33Z tseaver $
   32.20 +"""
   32.21 +
   32.22 +import unittest
   32.23 +import Testing
   32.24 +try:
   32.25 +    import Zope2
   32.26 +except ImportError: # BBB: for Zope 2.7
   32.27 +    import Zope as Zope2
   32.28 +Zope2.startup()
   32.29 +
   32.30 +from OFS.Folder import Folder
   32.31 +from Products.CMFSetup.tests.common import BaseRegistryTests
   32.32 +from Products.CMFSetup import EXTENSION
   32.33 +
   32.34 +from conformance import ConformsToIStepRegistry
   32.35 +from conformance import ConformsToIImportStepRegistry
   32.36 +from conformance import ConformsToIExportStepRegistry
   32.37 +from conformance import ConformsToIToolsetRegistry
   32.38 +from conformance import ConformsToIProfileRegistry
   32.39 +
   32.40 +
   32.41 +#==============================================================================
   32.42 +#   Dummy handlers
   32.43 +#==============================================================================
   32.44 +def ONE_FUNC( context ): pass
   32.45 +def TWO_FUNC( context ): pass
   32.46 +def THREE_FUNC( context ): pass
   32.47 +def FOUR_FUNC( context ): pass
   32.48 +
   32.49 +ONE_FUNC_NAME = '%s.%s' % ( __name__, ONE_FUNC.__name__ )
   32.50 +TWO_FUNC_NAME = '%s.%s' % ( __name__, TWO_FUNC.__name__ )
   32.51 +THREE_FUNC_NAME = '%s.%s' % ( __name__, THREE_FUNC.__name__ )
   32.52 +FOUR_FUNC_NAME = '%s.%s' % ( __name__, FOUR_FUNC.__name__ )
   32.53 +
   32.54 +
   32.55 +#==============================================================================
   32.56 +#   SSR tests
   32.57 +#==============================================================================
   32.58 +class ImportStepRegistryTests( BaseRegistryTests
   32.59 +                             , ConformsToIStepRegistry
   32.60 +                             , ConformsToIImportStepRegistry
   32.61 +                             ):
   32.62 +
   32.63 +    def _getTargetClass( self ):
   32.64 +
   32.65 +        from Products.CMFSetup.registry import ImportStepRegistry
   32.66 +        return ImportStepRegistry
   32.67 +
   32.68 +    def test_empty( self ):
   32.69 +
   32.70 +        registry = self._makeOne()
   32.71 +
   32.72 +        self.assertEqual( len( registry.listSteps() ), 0 )
   32.73 +        self.assertEqual( len( registry.listStepMetadata() ), 0 )
   32.74 +        self.assertEqual( len( registry.sortSteps() ), 0 )
   32.75 +
   32.76 +    def test_getStep_nonesuch( self ):
   32.77 +
   32.78 +        registry = self._makeOne()
   32.79 +
   32.80 +        self.assertEqual( registry.getStep( 'nonesuch' ), None )
   32.81 +        self.assertEqual( registry.getStep( 'nonesuch' ), None )
   32.82 +        default = object()
   32.83 +        self.failUnless( registry.getStepMetadata( 'nonesuch'
   32.84 +                                                 , default ) is default )
   32.85 +        self.failUnless( registry.getStep( 'nonesuch', default ) is default )
   32.86 +        self.failUnless( registry.getStepMetadata( 'nonesuch'
   32.87 +                                                 , default ) is default )
   32.88 +
   32.89 +    def test_getStep_defaulted( self ):
   32.90 +
   32.91 +        registry = self._makeOne()
   32.92 +        default = object()
   32.93 +
   32.94 +        self.failUnless( registry.getStep( 'nonesuch', default ) is default )
   32.95 +        self.assertEqual( registry.getStepMetadata( 'nonesuch', {} ), {} )
   32.96 +
   32.97 +    def test_registerStep_docstring( self ):
   32.98 +
   32.99 +        def func_with_doc( site ):
  32.100 +            """This is the first line.
  32.101 +
  32.102 +            This is the second line.
  32.103 +            """
  32.104 +        FUNC_NAME = '%s.%s' % ( __name__, func_with_doc.__name__ )
  32.105 +
  32.106 +        registry = self._makeOne()
  32.107 +
  32.108 +        registry.registerStep( id='docstring'
  32.109 +                             , version='1'
  32.110 +                             , handler=func_with_doc
  32.111 +                             , dependencies=()
  32.112 +                             )
  32.113 +
  32.114 +        info = registry.getStepMetadata( 'docstring' )
  32.115 +        self.assertEqual( info[ 'id' ], 'docstring' )
  32.116 +        self.assertEqual( info[ 'handler' ], FUNC_NAME )
  32.117 +        self.assertEqual( info[ 'dependencies' ], () )
  32.118 +        self.assertEqual( info[ 'title' ], 'This is the first line.' )
  32.119 +        self.assertEqual( info[ 'description' ] , 'This is the second line.' )
  32.120 +
  32.121 +    def test_registerStep_docstring_override( self ):
  32.122 +
  32.123 +        def func_with_doc( site ):
  32.124 +            """This is the first line.
  32.125 +
  32.126 +            This is the second line.
  32.127 +            """
  32.128 +        FUNC_NAME = '%s.%s' % ( __name__, func_with_doc.__name__ )
  32.129 +
  32.130 +        registry = self._makeOne()
  32.131 +
  32.132 +        registry.registerStep( id='docstring'
  32.133 +                             , version='1'
  32.134 +                             , handler=func_with_doc
  32.135 +                             , dependencies=()
  32.136 +                             , title='Title'
  32.137 +                             )
  32.138 +
  32.139 +        info = registry.getStepMetadata( 'docstring' )
  32.140 +        self.assertEqual( info[ 'id' ], 'docstring' )
  32.141 +        self.assertEqual( info[ 'handler' ], FUNC_NAME )
  32.142 +        self.assertEqual( info[ 'dependencies' ], () )
  32.143 +        self.assertEqual( info[ 'title' ], 'Title' )
  32.144 +        self.assertEqual( info[ 'description' ] , 'This is the second line.' )
  32.145 +
  32.146 +    def test_registerStep_single( self ):
  32.147 +
  32.148 +        registry = self._makeOne()
  32.149 +
  32.150 +        registry.registerStep( id='one'
  32.151 +                             , version='1'
  32.152 +                             , handler=ONE_FUNC
  32.153 +                             , dependencies=( 'two', 'three' )
  32.154 +                             , title='One Step'
  32.155 +                             , description='One small step'
  32.156 +                             )
  32.157 +
  32.158 +        steps = registry.listSteps()
  32.159 +        self.assertEqual( len( steps ), 1 )
  32.160 +        self.failUnless( 'one' in steps )
  32.161 +
  32.162 +        sorted = registry.sortSteps()
  32.163 +        self.assertEqual( len( sorted ), 1 )
  32.164 +        self.assertEqual( sorted[ 0 ], 'one' )
  32.165 +
  32.166 +        self.assertEqual( registry.getStep( 'one' ), ONE_FUNC )
  32.167 +
  32.168 +        info = registry.getStepMetadata( 'one' )
  32.169 +        self.assertEqual( info[ 'id' ], 'one' )
  32.170 +        self.assertEqual( info[ 'version' ], '1' )
  32.171 +        self.assertEqual( info[ 'handler' ], ONE_FUNC_NAME )
  32.172 +        self.assertEqual( info[ 'dependencies' ], ( 'two', 'three' ) )
  32.173 +        self.assertEqual( info[ 'title' ], 'One Step' )
  32.174 +        self.assertEqual( info[ 'description' ], 'One small step' )
  32.175 +
  32.176 +        info_list = registry.listStepMetadata()
  32.177 +        self.assertEqual( len( info_list ), 1 )
  32.178 +        self.assertEqual( info, info_list[ 0 ] )
  32.179 +
  32.180 +    def test_registerStep_conflict( self ):
  32.181 +
  32.182 +        registry = self._makeOne()
  32.183 +
  32.184 +        registry.registerStep( id='one', version='1', handler=ONE_FUNC )
  32.185 +
  32.186 +        self.assertRaises( KeyError
  32.187 +                         , registry.registerStep
  32.188 +                         , id='one'
  32.189 +                         , version='0'
  32.190 +                         , handler=ONE_FUNC
  32.191 +                         )
  32.192 +
  32.193 +        registry.registerStep( id='one', version='1', handler=ONE_FUNC )
  32.194 +
  32.195 +        info_list = registry.listStepMetadata()
  32.196 +        self.assertEqual( len( info_list ), 1 )
  32.197 +
  32.198 +    def test_registerStep_replacement( self ):
  32.199 +
  32.200 +        registry = self._makeOne()
  32.201 +
  32.202 +        registry.registerStep( id='one'
  32.203 +                             , version='1'
  32.204 +                             , handler=ONE_FUNC
  32.205 +                             , dependencies=( 'two', 'three' )
  32.206 +                             , title='One Step'
  32.207 +                             , description='One small step'
  32.208 +                             )
  32.209 +
  32.210 +        registry.registerStep( id='one'
  32.211 +                             , version='1.1'
  32.212 +                             , handler=ONE_FUNC
  32.213 +                             , dependencies=()
  32.214 +                             , title='Leads to Another'
  32.215 +                             , description='Another small step'
  32.216 +                             )
  32.217 +
  32.218 +        info = registry.getStepMetadata( 'one' )
  32.219 +        self.assertEqual( info[ 'id' ], 'one' )
  32.220 +        self.assertEqual( info[ 'version' ], '1.1' )
  32.221 +        self.assertEqual( info[ 'dependencies' ], () )
  32.222 +        self.assertEqual( info[ 'title' ], 'Leads to Another' )
  32.223 +        self.assertEqual( info[ 'description' ], 'Another small step' )
  32.224 +
  32.225 +    def test_registerStep_multiple( self ):
  32.226 +
  32.227 +        registry = self._makeOne()
  32.228 +
  32.229 +        registry.registerStep( id='one'
  32.230 +                             , version='1'
  32.231 +                             , handler=ONE_FUNC
  32.232 +                             , dependencies=()
  32.233 +                             )
  32.234 +
  32.235 +        registry.registerStep( id='two'
  32.236 +                             , version='2'
  32.237 +                             , handler=TWO_FUNC
  32.238 +                             , dependencies=()
  32.239 +                             )
  32.240 +
  32.241 +        registry.registerStep( id='three'
  32.242 +                             , version='3'
  32.243 +                             , handler=THREE_FUNC
  32.244 +                             , dependencies=()
  32.245 +                             )
  32.246 +
  32.247 +        steps = registry.listSteps()
  32.248 +        self.assertEqual( len( steps ), 3 )
  32.249 +        self.failUnless( 'one' in steps )
  32.250 +        self.failUnless( 'two' in steps )
  32.251 +        self.failUnless( 'three' in steps )
  32.252 +
  32.253 +    def test_sortStep_simple( self ):
  32.254 +
  32.255 +        registry = self._makeOne()
  32.256 +
  32.257 +        registry.registerStep( id='one'
  32.258 +                             , version='1'
  32.259 +                             , handler=ONE_FUNC
  32.260 +                             , dependencies=( 'two', )
  32.261 +                             )
  32.262 +
  32.263 +        registry.registerStep( id='two'
  32.264 +                             , version='2'
  32.265 +                             , handler=TWO_FUNC
  32.266 +                             , dependencies=()
  32.267 +                             )
  32.268 +
  32.269 +        steps = registry.sortSteps()
  32.270 +        self.assertEqual( len( steps ), 2 )
  32.271 +        one = steps.index( 'one' )
  32.272 +        two = steps.index( 'two' )
  32.273 +
  32.274 +        self.failUnless( 0 <= two < one )
  32.275 +
  32.276 +    def test_sortStep_chained( self ):
  32.277 +
  32.278 +        registry = self._makeOne()
  32.279 +
  32.280 +        registry.registerStep( id='one'
  32.281 +                             , version='1'
  32.282 +                             , handler=ONE_FUNC
  32.283 +                             , dependencies=( 'two', )
  32.284 +                             , title='One small step'
  32.285 +                             )
  32.286 +
  32.287 +        registry.registerStep( id='two'
  32.288 +                             , version='2'
  32.289 +                             , handler=TWO_FUNC
  32.290 +                             , dependencies=( 'three', )
  32.291 +                             , title='Texas two step'
  32.292 +                             )
  32.293 +
  32.294 +        registry.registerStep( id='three'
  32.295 +                             , version='3'
  32.296 +                             , handler=THREE_FUNC
  32.297 +                             , dependencies=()
  32.298 +                             , title='Gimme three steps'
  32.299 +                             )
  32.300 +
  32.301 +        steps = registry.sortSteps()
  32.302 +        self.assertEqual( len( steps ), 3 )
  32.303 +        one = steps.index( 'one' )
  32.304 +        two = steps.index( 'two' )
  32.305 +        three = steps.index( 'three' )
  32.306 +
  32.307 +        self.failUnless( 0 <= three < two < one )
  32.308 +
  32.309 +    def test_sortStep_complex( self ):
  32.310 +
  32.311 +        registry = self._makeOne()
  32.312 +
  32.313 +        registry.registerStep( id='one'
  32.314 +                             , version='1'
  32.315 +                             , handler=ONE_FUNC
  32.316 +                             , dependencies=( 'two', )
  32.317 +                             , title='One small step'
  32.318 +                             )
  32.319 +
  32.320 +        registry.registerStep( id='two'
  32.321 +                             , version='2'
  32.322 +                             , handler=TWO_FUNC
  32.323 +                             , dependencies=( 'four', )
  32.324 +                             , title='Texas two step'
  32.325 +                             )
  32.326 +
  32.327 +        registry.registerStep( id='three'
  32.328 +                             , version='3'
  32.329 +                             , handler=THREE_FUNC
  32.330 +                             , dependencies=( 'four', )
  32.331 +                             , title='Gimme three steps'
  32.332 +                             )
  32.333 +
  32.334 +        registry.registerStep( id='four'
  32.335 +                             , version='4'
  32.336 +                             , handler=FOUR_FUNC
  32.337 +                             , dependencies=()
  32.338 +                             , title='Four step program'
  32.339 +                             )
  32.340 +
  32.341 +        steps = registry.sortSteps()
  32.342 +        self.assertEqual( len( steps ), 4 )
  32.343 +        one = steps.index( 'one' )
  32.344 +        two = steps.index( 'two' )
  32.345 +        three = steps.index( 'three' )
  32.346 +        four = steps.index( 'four' )
  32.347 +
  32.348 +        self.failUnless( 0 <= four < two < one )
  32.349 +        self.failUnless( 0 <= four < three )
  32.350 +
  32.351 +    def test_sortStep_equivalence( self ):
  32.352 +
  32.353 +        registry = self._makeOne()
  32.354 +
  32.355 +        registry.registerStep( id='one'
  32.356 +                             , version='1'
  32.357 +                             , handler=ONE_FUNC
  32.358 +                             , dependencies=( 'two', 'three' )
  32.359 +                             , title='One small step'
  32.360 +                             )
  32.361 +
  32.362 +        registry.registerStep( id='two'
  32.363 +                             , version='2'
  32.364 +                             , handler=TWO_FUNC
  32.365 +                             , dependencies=( 'four', )
  32.366 +                             , title='Texas two step'
  32.367 +                             )
  32.368 +
  32.369 +        registry.registerStep( id='three'
  32.370 +                             , version='3'
  32.371 +                             , handler=THREE_FUNC
  32.372 +                             , dependencies=( 'four', )
  32.373 +                             , title='Gimme three steps'
  32.374 +                             )
  32.375 +
  32.376 +        registry.registerStep( id='four'
  32.377 +                             , version='4'
  32.378 +                             , handler=FOUR_FUNC
  32.379 +                             , dependencies=()
  32.380 +                             , title='Four step program'
  32.381 +                             )
  32.382 +
  32.383 +        steps = registry.sortSteps()
  32.384 +        self.assertEqual( len( steps ), 4 )
  32.385 +        one = steps.index( 'one' )
  32.386 +        two = steps.index( 'two' )
  32.387 +        three = steps.index( 'three' )
  32.388 +        four = steps.index( 'four' )
  32.389 +
  32.390 +        self.failUnless( 0 <= four < two < one )
  32.391 +        self.failUnless( 0 <= four < three < one )
  32.392 +
  32.393 +    def test_checkComplete_simple( self ):
  32.394 +
  32.395 +        registry = self._makeOne()
  32.396 +
  32.397 +        registry.registerStep( id='one'
  32.398 +                             , version='1'
  32.399 +                             , handler=ONE_FUNC
  32.400 +                             , dependencies=( 'two', )
  32.401 +                             )
  32.402 +
  32.403 +        incomplete = registry.checkComplete()
  32.404 +        self.assertEqual( len( incomplete ), 1 )
  32.405 +        self.failUnless( ( 'one', 'two' ) in incomplete )
  32.406 +
  32.407 +        registry.registerStep( id='two'
  32.408 +                             , version='2'
  32.409 +                             , handler=TWO_FUNC
  32.410 +                             , dependencies=()
  32.411 +                             )
  32.412 +
  32.413 +        self.assertEqual( len( registry.checkComplete() ), 0 )
  32.414 +
  32.415 +    def test_checkComplete_double( self ):
  32.416 +
  32.417 +        registry = self._makeOne()
  32.418 +
  32.419 +        registry.registerStep( id='one'
  32.420 +                             , version='1'
  32.421 +                             , handler=ONE_FUNC
  32.422 +                             , dependencies=( 'two', 'three' )
  32.423 +                             )
  32.424 +
  32.425 +        incomplete = registry.checkComplete()
  32.426 +        self.assertEqual( len( incomplete ), 2 )
  32.427 +        self.failUnless( ( 'one', 'two' ) in incomplete )
  32.428 +        self.failUnless( ( 'one', 'three' ) in incomplete )
  32.429 +
  32.430 +        registry.registerStep( id='two'
  32.431 +                             , version='2'
  32.432 +                             , handler=TWO_FUNC
  32.433 +                             , dependencies=()
  32.434 +                             )
  32.435 +
  32.436 +        incomplete = registry.checkComplete()
  32.437 +        self.assertEqual( len( incomplete ), 1 )
  32.438 +        self.failUnless( ( 'one', 'three' ) in incomplete )
  32.439 +
  32.440 +        registry.registerStep( id='three'
  32.441 +                             , version='3'
  32.442 +                             , handler=THREE_FUNC
  32.443 +                             , dependencies=()
  32.444 +                             )
  32.445 +
  32.446 +        self.assertEqual( len( registry.checkComplete() ), 0 )
  32.447 +
  32.448 +        registry.registerStep( id='two'
  32.449 +                             , version='2.1'
  32.450 +                             , handler=TWO_FUNC
  32.451 +                             , dependencies=( 'four', )
  32.452 +                             )
  32.453 +
  32.454 +        incomplete = registry.checkComplete()
  32.455 +        self.assertEqual( len( incomplete ), 1 )
  32.456 +        self.failUnless( ( 'two', 'four' ) in incomplete )
  32.457 +
  32.458 +    def test_generateXML_empty( self ):
  32.459 +
  32.460 +        registry = self._makeOne().__of__( self.root )
  32.461 +
  32.462 +        xml = registry.generateXML()
  32.463 +
  32.464 +        self._compareDOM( registry.generateXML(), _EMPTY_IMPORT_XML )
  32.465 +
  32.466 +    def test_generateXML_single( self ):
  32.467 +
  32.468 +        registry = self._makeOne().__of__( self.root )
  32.469 +
  32.470 +        registry.registerStep( id='one'
  32.471 +                             , version='1'
  32.472 +                             , handler=ONE_FUNC
  32.473 +                             , dependencies=()
  32.474 +                             , title='One Step'
  32.475 +                             , description='One small step'
  32.476 +                             )
  32.477 +
  32.478 +        self._compareDOM( registry.generateXML(), _SINGLE_IMPORT_XML )
  32.479 +
  32.480 +    def test_generateXML_ordered( self ):
  32.481 +
  32.482 +        registry = self._makeOne().__of__( self.root )
  32.483 +
  32.484 +        registry.registerStep( id='one'
  32.485 +                             , version='1'
  32.486 +                             , handler=ONE_FUNC
  32.487 +                             , dependencies=( 'two', )
  32.488 +                             , title='One Step'
  32.489 +                             , description='One small step'
  32.490 +                             )
  32.491 +
  32.492 +        registry.registerStep( id='two'
  32.493 +                             , version='2'
  32.494 +                             , handler=TWO_FUNC
  32.495 +                             , dependencies=( 'three', )
  32.496 +                             , title='Two Steps'
  32.497 +                             , description='Texas two step'
  32.498 +                             )
  32.499 +
  32.500 +        registry.registerStep( id='three'
  32.501 +                             , version='3'
  32.502 +                             , handler=THREE_FUNC
  32.503 +                             , dependencies=()
  32.504 +                             , title='Three Steps'
  32.505 +                             , description='Gimme three steps'
  32.506 +                             )
  32.507 +
  32.508 +        self._compareDOM( registry.generateXML(), _ORDERED_IMPORT_XML )
  32.509 +
  32.510 +    def test_parseXML_empty( self ):
  32.511 +
  32.512 +        registry = self._makeOne().__of__( self.root )
  32.513 +
  32.514 +        registry.registerStep( id='one'
  32.515 +                             , version='1'
  32.516 +                             , handler=ONE_FUNC
  32.517 +                             , dependencies=()
  32.518 +                             , description='One small step'
  32.519 +                             )
  32.520 +
  32.521 +        info_list = registry.parseXML( _EMPTY_IMPORT_XML )
  32.522 +
  32.523 +        self.assertEqual( len( info_list ), 0 )
  32.524 +
  32.525 +    def test_parseXML_single( self ):
  32.526 +
  32.527 +        registry = self._makeOne().__of__( self.root )
  32.528 +
  32.529 +        registry.registerStep( id='two'
  32.530 +                             , version='2'
  32.531 +                             , handler=TWO_FUNC
  32.532 +                             , dependencies=()
  32.533 +                             , title='Two Steps'
  32.534 +                             , description='Texas two step'
  32.535 +                             )
  32.536 +
  32.537 +        info_list = registry.parseXML( _SINGLE_IMPORT_XML )
  32.538 +
  32.539 +        self.assertEqual( len( info_list ), 1 )
  32.540 +
  32.541 +        info = info_list[ 0 ]
  32.542 +        self.assertEqual( info[ 'id' ], 'one' )
  32.543 +        self.assertEqual( info[ 'version' ], '1' )
  32.544 +        self.assertEqual( info[ 'handler' ], ONE_FUNC_NAME )
  32.545 +        self.assertEqual( info[ 'dependencies' ], () )
  32.546 +        self.assertEqual( info[ 'title' ], 'One Step' )
  32.547 +        self.failUnless( 'One small step' in info[ 'description' ] )
  32.548 +
  32.549 +
  32.550 +_EMPTY_IMPORT_XML = """\
  32.551 +<?xml version="1.0"?>
  32.552 +<import-steps>
  32.553 +</import-steps>
  32.554 +"""
  32.555 +
  32.556 +_SINGLE_IMPORT_XML = """\
  32.557 +<?xml version="1.0"?>
  32.558 +<import-steps>
  32.559 + <import-step id="one"
  32.560 +             version="1"
  32.561 +             handler="%s"
  32.562 +             title="One Step">
  32.563 +  One small step
  32.564 + </import-step>
  32.565 +</import-steps>
  32.566 +""" % ( ONE_FUNC_NAME, )
  32.567 +
  32.568 +_ORDERED_IMPORT_XML = """\
  32.569 +<?xml version="1.0"?>
  32.570 +<import-steps>
  32.571 + <import-step id="one"
  32.572 +             version="1"
  32.573 +             handler="%s"
  32.574 +             title="One Step">
  32.575 +  <dependency step="two" />
  32.576 +  One small step
  32.577 + </import-step>
  32.578 + <import-step id="three"
  32.579 +             version="3"
  32.580 +             handler="%s"
  32.581 +             title="Three Steps">
  32.582 +  Gimme three steps
  32.583 + </import-step>
  32.584 + <import-step id="two"
  32.585 +             version="2"
  32.586 +             handler="%s"
  32.587 +             title="Two Steps">
  32.588 +  <dependency step="three" />
  32.589 +  Texas two step
  32.590 + </import-step>
  32.591 +</import-steps>
  32.592 +""" % ( ONE_FUNC_NAME, THREE_FUNC_NAME, TWO_FUNC_NAME )
  32.593 +
  32.594 +
  32.595 +#==============================================================================
  32.596 +#   ESR tests
  32.597 +#==============================================================================
  32.598 +class ExportStepRegistryTests( BaseRegistryTests
  32.599 +                             , ConformsToIStepRegistry
  32.600 +                             , ConformsToIExportStepRegistry
  32.601 +                             ):
  32.602 +
  32.603 +    def _getTargetClass( self ):
  32.604 +
  32.605 +        from Products.CMFSetup.registry import ExportStepRegistry
  32.606 +        return ExportStepRegistry
  32.607 +
  32.608 +    def _makeOne( self, *args, **kw ):
  32.609 +
  32.610 +        return self._getTargetClass()( *args, **kw )
  32.611 +
  32.612 +    def test_empty( self ):
  32.613 +
  32.614 +        registry = self._makeOne()
  32.615 +        self.assertEqual( len( registry.listSteps() ), 0 )
  32.616 +        self.assertEqual( len( registry.listStepMetadata() ), 0 )
  32.617 +
  32.618 +    def test_getStep_nonesuch( self ):
  32.619 +
  32.620 +        registry = self._makeOne()
  32.621 +        self.assertEqual( registry.getStep( 'nonesuch' ), None )
  32.622 +
  32.623 +    def test_getStep_defaulted( self ):
  32.624 +
  32.625 +        registry = self._makeOne()
  32.626 +        default = lambda x: false
  32.627 +        self.assertEqual( registry.getStep( 'nonesuch', default ), default )
  32.628 +
  32.629 +    def test_getStepMetadata_nonesuch( self ):
  32.630 +
  32.631 +        registry = self._makeOne()
  32.632 +        self.assertEqual( registry.getStepMetadata( 'nonesuch' ), None )
  32.633 +
  32.634 +    def test_getStepMetadata_defaulted( self ):
  32.635 +
  32.636 +        registry = self._makeOne()
  32.637 +        self.assertEqual( registry.getStepMetadata( 'nonesuch', {} ), {} )
  32.638 +
  32.639 +    def test_registerStep_simple( self ):
  32.640 +
  32.641 +        registry = self._makeOne()
  32.642 +        registry.registerStep( 'one', ONE_FUNC )
  32.643 +        info = registry.getStepMetadata( 'one', {} )
  32.644 +
  32.645 +        self.assertEqual( info[ 'id' ], 'one' )
  32.646 +        self.assertEqual( info[ 'handler' ], ONE_FUNC_NAME )
  32.647 +        self.assertEqual( info[ 'title' ], 'one' )
  32.648 +        self.assertEqual( info[ 'description' ], '' )
  32.649 +
  32.650 +    def test_registerStep_docstring( self ):
  32.651 +
  32.652 +        def func_with_doc( site ):
  32.653 +            """This is the first line.
  32.654 +
  32.655 +            This is the second line.
  32.656 +            """
  32.657 +        FUNC_NAME = '%s.%s' % ( __name__, func_with_doc.__name__ )
  32.658 +
  32.659 +        registry = self._makeOne()
  32.660 +        registry.registerStep( 'one', func_with_doc )
  32.661 +        info = registry.getStepMetadata( 'one', {} )
  32.662 +
  32.663 +        self.assertEqual( info[ 'id' ], 'one' )
  32.664 +        self.assertEqual( info[ 'handler' ], FUNC_NAME )
  32.665 +        self.assertEqual( info[ 'title' ], 'This is the first line.' )
  32.666 +        self.assertEqual( info[ 'description' ] , 'This is the second line.' )
  32.667 +
  32.668 +    def test_registerStep_docstring_with_override( self ):
  32.669 +
  32.670 +        def func_with_doc( site ):
  32.671 +            """This is the first line.
  32.672 +
  32.673 +            This is the second line.
  32.674 +            """
  32.675 +        FUNC_NAME = '%s.%s' % ( __name__, func_with_doc.__name__ )
  32.676 +
  32.677 +        registry = self._makeOne()
  32.678 +        registry.registerStep( 'one', func_with_doc
  32.679 +                               , description='Description' )
  32.680 +        info = registry.getStepMetadata( 'one', {} )
  32.681 +
  32.682 +        self.assertEqual( info[ 'id' ], 'one' )
  32.683 +        self.assertEqual( info[ 'handler' ], FUNC_NAME )
  32.684 +        self.assertEqual( info[ 'title' ], 'This is the first line.' )
  32.685 +        self.assertEqual( info[ 'description' ], 'Description' )
  32.686 +
  32.687 +    def test_registerStep_collision( self ):
  32.688 +
  32.689 +        registry = self._makeOne()
  32.690 +        registry.registerStep( 'one', ONE_FUNC )
  32.691 +        registry.registerStep( 'one', TWO_FUNC )
  32.692 +        info = registry.getStepMetadata( 'one', {} )
  32.693 +
  32.694 +        self.assertEqual( info[ 'id' ], 'one' )
  32.695 +        self.assertEqual( info[ 'handler' ], TWO_FUNC_NAME )
  32.696 +        self.assertEqual( info[ 'title' ], 'one' )
  32.697 +        self.assertEqual( info[ 'description' ], '' )
  32.698 +
  32.699 +    def test_generateXML_empty( self ):
  32.700 +
  32.701 +        registry = self._makeOne().__of__( self.root )
  32.702 +
  32.703 +        xml = registry.generateXML()
  32.704 +
  32.705 +        self._compareDOM( registry.generateXML(), _EMPTY_EXPORT_XML )
  32.706 +
  32.707 +    def test_generateXML_single( self ):
  32.708 +
  32.709 +        registry = self._makeOne().__of__( self.root )
  32.710 +
  32.711 +        registry.registerStep( id='one'
  32.712 +                             , handler=ONE_FUNC
  32.713 +                             , title='One Step'
  32.714 +                             , description='One small step'
  32.715 +                             )
  32.716 +
  32.717 +        self._compareDOM( registry.generateXML(), _SINGLE_EXPORT_XML )
  32.718 +
  32.719 +    def test_generateXML_ordered( self ):
  32.720 +
  32.721 +        registry = self._makeOne().__of__( self.root )
  32.722 +
  32.723 +        registry.registerStep( id='one'
  32.724 +                             , handler=ONE_FUNC
  32.725 +                             , title='One Step'
  32.726 +                             , description='One small step'
  32.727 +                             )
  32.728 +
  32.729 +        registry.registerStep( id='two'
  32.730 +                             , handler=TWO_FUNC
  32.731 +                             , title='Two Steps'
  32.732 +                             , description='Texas two step'
  32.733 +                             )
  32.734 +
  32.735 +        registry.registerStep( id='three'
  32.736 +                             , handler=THREE_FUNC
  32.737 +                             , title='Three Steps'
  32.738 +                             , description='Gimme three steps'
  32.739 +                             )
  32.740 +
  32.741 +        self._compareDOM( registry.generateXML(), _ORDERED_EXPORT_XML )
  32.742 +
  32.743 +    def test_parseXML_empty( self ):
  32.744 +
  32.745 +        registry = self._makeOne().__of__( self.root )
  32.746 +
  32.747 +        registry.registerStep( id='one'
  32.748 +                             , handler=ONE_FUNC
  32.749 +                             , description='One small step'
  32.750 +                             )
  32.751 +
  32.752 +        info_list = registry.parseXML( _EMPTY_EXPORT_XML )
  32.753 +
  32.754 +        self.assertEqual( len( info_list ), 0 )
  32.755 +
  32.756 +    def test_parseXML_single( self ):
  32.757 +
  32.758 +        registry = self._makeOne().__of__( self.root )
  32.759 +
  32.760 +        registry.registerStep( id='two'
  32.761 +                             , handler=TWO_FUNC
  32.762 +                             , title='Two Steps'
  32.763 +                             , description='Texas two step'
  32.764 +                             )
  32.765 +
  32.766 +        info_list = registry.parseXML( _SINGLE_EXPORT_XML )
  32.767 +
  32.768 +        self.assertEqual( len( info_list ), 1 )
  32.769 +
  32.770 +        info = info_list[ 0 ]
  32.771 +        self.assertEqual( info[ 'id' ], 'one' )
  32.772 +        self.assertEqual( info[ 'handler' ], ONE_FUNC_NAME )
  32.773 +        self.assertEqual( info[ 'title' ], 'One Step' )
  32.774 +        self.failUnless( 'One small step' in info[ 'description' ] )
  32.775 +
  32.776 +    def test_parseXML_single_as_ascii( self ):
  32.777 +
  32.778 +        registry = self._makeOne().__of__( self.root )
  32.779 +
  32.780 +        registry.registerStep( id='two'
  32.781 +                             , handler=TWO_FUNC
  32.782 +                             , title='Two Steps'
  32.783 +                             , description='Texas two step'
  32.784 +                             )
  32.785 +
  32.786 +        info_list = registry.parseXML( _SINGLE_EXPORT_XML, encoding='ascii' )
  32.787 +
  32.788 +        self.assertEqual( len( info_list ), 1 )
  32.789 +
  32.790 +        info = info_list[ 0 ]
  32.791 +        self.assertEqual( info[ 'id' ], 'one' )
  32.792 +        self.assertEqual( info[ 'handler' ], ONE_FUNC_NAME )
  32.793 +        self.assertEqual( info[ 'title' ], 'One Step' )
  32.794 +        self.failUnless( 'One small step' in info[ 'description' ] )
  32.795 +
  32.796 +
  32.797 +_EMPTY_EXPORT_XML = """\
  32.798 +<?xml version="1.0"?>
  32.799 +<export-steps>
  32.800 +</export-steps>
  32.801 +"""
  32.802 +
  32.803 +_SINGLE_EXPORT_XML = """\
  32.804 +<?xml version="1.0"?>
  32.805 +<export-steps>
  32.806 + <export-step id="one"
  32.807 +                handler="%s"
  32.808 +                title="One Step">
  32.809 +  One small step
  32.810 + </export-step>
  32.811 +</export-steps>
  32.812 +""" % ( ONE_FUNC_NAME, )
  32.813 +
  32.814 +_ORDERED_EXPORT_XML = """\
  32.815 +<?xml version="1.0"?>
  32.816 +<export-steps>
  32.817 + <export-step id="one"
  32.818 +                handler="%s"
  32.819 +                title="One Step">
  32.820 +  One small step
  32.821 + </export-step>
  32.822 + <export-step id="three"
  32.823 +                handler="%s"
  32.824 +                title="Three Steps">
  32.825 +  Gimme three steps
  32.826 + </export-step>
  32.827 + <export-step id="two"
  32.828 +                handler="%s"
  32.829 +                title="Two Steps">
  32.830 +  Texas two step
  32.831 + </export-step>
  32.832 +</export-steps>
  32.833 +""" % ( ONE_FUNC_NAME, THREE_FUNC_NAME, TWO_FUNC_NAME )
  32.834 +
  32.835 +#==============================================================================
  32.836 +#   ToolsetRegistry tests
  32.837 +#==============================================================================
  32.838 +class ToolsetRegistryTests( BaseRegistryTests
  32.839 +                          , ConformsToIToolsetRegistry
  32.840 +                          ):
  32.841 +
  32.842 +    def _getTargetClass( self ):
  32.843 +
  32.844 +        from Products.CMFSetup.registry import ToolsetRegistry
  32.845 +        return ToolsetRegistry
  32.846 +
  32.847 +    def _initSite( self ):
  32.848 +
  32.849 +        self.root.site = Folder( id='site' )
  32.850 +        site = self.root.site
  32.851 +
  32.852 +        return site
  32.853 +
  32.854 +    def test_empty( self ):
  32.855 +
  32.856 +        site = self._initSite()
  32.857 +        configurator = self._makeOne().__of__( site )
  32.858 +
  32.859 +        self.assertEqual( len( configurator.listForbiddenTools() ), 0 )
  32.860 +        self.assertEqual( len( configurator.listRequiredTools() ), 0 )
  32.861 +        self.assertEqual( len( configurator.listRequiredToolInfo() ), 0 )
  32.862 +
  32.863 +        self.assertRaises( KeyError
  32.864 +                         , configurator.getRequiredToolInfo, 'nonesuch' )
  32.865 +
  32.866 +    def test_addForbiddenTool_multiple( self ):
  32.867 +
  32.868 +        VERBOTTEN = ( 'foo', 'bar', 'bam' )
  32.869 +
  32.870 +        site = self._initSite()
  32.871 +        configurator = self._makeOne().__of__( site )
  32.872 +
  32.873 +        for verbotten in VERBOTTEN:
  32.874 +            configurator.addForbiddenTool( verbotten )
  32.875 +
  32.876 +        self.assertEqual( len( configurator.listForbiddenTools() )
  32.877 +                        , len( VERBOTTEN ) )
  32.878 +
  32.879 +        for verbotten in configurator.listForbiddenTools():
  32.880 +            self.failUnless( verbotten in VERBOTTEN )
  32.881 +
  32.882 +    def test_addForbiddenTool_duplicate( self ):
  32.883 +
  32.884 +        site = self._initSite()
  32.885 +        configurator = self._makeOne().__of__( site )
  32.886 +
  32.887 +        configurator.addForbiddenTool( 'once' )
  32.888 +        configurator.addForbiddenTool( 'once' )
  32.889 +
  32.890 +        self.assertEqual( len( configurator.listForbiddenTools() ), 1 )
  32.891 +
  32.892 +    def test_addForbiddenTool_but_required( self ):
  32.893 +
  32.894 +        site = self._initSite()
  32.895 +        configurator = self._makeOne().__of__( site )
  32.896 +
  32.897 +        configurator.addRequiredTool( 'required', 'some.dotted.name' )
  32.898 +
  32.899 +        self.assertRaises( ValueError
  32.900 +                         , configurator.addForbiddenTool, 'required' )
  32.901 +
  32.902 +    def test_addRequiredTool_multiple( self ):
  32.903 +
  32.904 +        REQUIRED = ( ( 'one', 'path.to.one' )
  32.905 +                   , ( 'two', 'path.to.two' )
  32.906 +                   , ( 'three', 'path.to.three' )
  32.907 +                   )
  32.908 +
  32.909 +        site = self._initSite()
  32.910 +        configurator = self._makeOne().__of__( site )
  32.911 +
  32.912 +        for tool_id, dotted_name in REQUIRED:
  32.913 +            configurator.addRequiredTool( tool_id, dotted_name )
  32.914 +
  32.915 +        self.assertEqual( len( configurator.listRequiredTools() )
  32.916 +                        , len( REQUIRED ) )
  32.917 +
  32.918 +        for id in [ x[0] for x in REQUIRED ]:
  32.919 +            self.failUnless( id in configurator.listRequiredTools() )
  32.920 +
  32.921 +        self.assertEqual( len( configurator.listRequiredToolInfo() )
  32.922 +                        , len( REQUIRED ) )
  32.923 +
  32.924 +        for tool_id, dotted_name in REQUIRED:
  32.925 +            info = configurator.getRequiredToolInfo( tool_id )
  32.926 +            self.assertEqual( info[ 'id' ], tool_id )
  32.927 +            self.assertEqual( info[ 'class' ], dotted_name )
  32.928 +
  32.929 +    def test_addRequiredTool_duplicate( self ):
  32.930 +
  32.931 +        site = self._initSite()
  32.932 +        configurator = self._makeOne().__of__( site )
  32.933 +
  32.934 +        configurator.addRequiredTool( 'required', 'some.dotted.name' )
  32.935 +        configurator.addRequiredTool( 'required', 'another.name' )
  32.936 +
  32.937 +        info = configurator.getRequiredToolInfo( 'required' )
  32.938 +        self.assertEqual( info[ 'id' ], 'required' )
  32.939 +        self.assertEqual( info[ 'class' ], 'another.name' )
  32.940 +
  32.941 +    def test_addRequiredTool_but_forbidden( self ):
  32.942 +
  32.943 +        site = self._initSite()
  32.944 +        configurator = self._makeOne().__of__( site )
  32.945 +
  32.946 +        configurator.addForbiddenTool( 'forbidden' )
  32.947 +
  32.948 +        self.assertRaises( ValueError
  32.949 +                         , configurator.addRequiredTool
  32.950 +                         , 'forbidden'
  32.951 +                         , 'a.name'
  32.952 +                         )
  32.953 +
  32.954 +    def test_generateXML_empty( self ):
  32.955 +
  32.956 +        site = self._initSite()
  32.957 +        configurator = self._makeOne().__of__( site )
  32.958 +
  32.959 +        self._compareDOM( configurator.generateXML(), _EMPTY_TOOLSET_XML )
  32.960 +
  32.961 +    def test_generateXML_normal( self ):
  32.962 +
  32.963 +        site = self._initSite()
  32.964 +        configurator = self._makeOne().__of__( site )
  32.965 +
  32.966 +        configurator.addForbiddenTool( 'doomed' )
  32.967 +        configurator.addRequiredTool( 'mandatory', 'path.to.one' )
  32.968 +        configurator.addRequiredTool( 'obligatory', 'path.to.another' )
  32.969 +
  32.970 +        configurator.parseXML( _NORMAL_TOOLSET_XML )
  32.971 +
  32.972 +    def test_parseXML_empty( self ):
  32.973 +
  32.974 +        site = self._initSite()
  32.975 +        configurator = self._makeOne().__of__( site )
  32.976 +
  32.977 +        configurator.parseXML( _EMPTY_TOOLSET_XML )
  32.978 +
  32.979 +        self.assertEqual( len( configurator.listForbiddenTools() ), 0 )
  32.980 +        self.assertEqual( len( configurator.listRequiredTools() ), 0 )
  32.981 +
  32.982 +    def test_parseXML_normal( self ):
  32.983 +
  32.984 +        site = self._initSite()
  32.985 +        configurator = self._makeOne().__of__( site )
  32.986 +
  32.987 +        configurator.parseXML( _NORMAL_TOOLSET_XML )
  32.988 +
  32.989 +        self.assertEqual( len( configurator.listForbiddenTools() ), 1 )
  32.990 +        self.failUnless( 'doomed' in configurator.listForbiddenTools() )
  32.991 +
  32.992 +        self.assertEqual( len( configurator.listRequiredTools() ), 2 )
  32.993 +
  32.994 +        self.failUnless( 'mandatory' in configurator.listRequiredTools() )
  32.995 +        info = configurator.getRequiredToolInfo( 'mandatory' )
  32.996 +        self.assertEqual( info[ 'class' ], 'path.to.one' )
  32.997 +
  32.998 +        self.failUnless( 'obligatory' in configurator.listRequiredTools() )
  32.999 +        info = configurator.getRequiredToolInfo( 'obligatory' )
 32.1000 +        self.assertEqual( info[ 'class' ], 'path.to.another' )
 32.1001 +
 32.1002 +    def test_parseXML_confused( self ):
 32.1003 +
 32.1004 +        site = self._initSite()
 32.1005 +        configurator = self._makeOne().__of__( site )
 32.1006 +
 32.1007 +        self.assertRaises( ValueError
 32.1008 +                         , configurator.parseXML, _CONFUSED_TOOLSET_XML )
 32.1009 +
 32.1010 +
 32.1011 +_EMPTY_TOOLSET_XML = """\
 32.1012 +<?xml version="1.0"?>
 32.1013 +<tool-setup>
 32.1014 +</tool-setup>
 32.1015 +"""
 32.1016 +
 32.1017 +_NORMAL_TOOLSET_XML = """\
 32.1018 +<?xml version="1.0"?>
 32.1019 +<tool-setup>
 32.1020 + <forbidden tool_id="doomed" />
 32.1021 + <required tool_id="mandatory" class="path.to.one" />
 32.1022 + <required tool_id="obligatory" class="path.to.another" />
 32.1023 +</tool-setup>
 32.1024 +"""
 32.1025 +
 32.1026 +_CONFUSED_TOOLSET_XML = """\
 32.1027 +<?xml version="1.0"?>
 32.1028 +<tool-setup>
 32.1029 + <forbidden tool_id="confused" />
 32.1030 + <required tool_id="confused" class="path.to.one" />
 32.1031 +</tool-setup>
 32.1032 +"""
 32.1033 +
 32.1034 +class ProfileRegistryTests( BaseRegistryTests
 32.1035 +                          , ConformsToIProfileRegistry
 32.1036 +                          ):
 32.1037 +
 32.1038 +    def _getTargetClass( self ):
 32.1039 +
 32.1040 +        from Products.CMFSetup.registry import ProfileRegistry
 32.1041 +        return ProfileRegistry
 32.1042 +
 32.1043 +    def test_empty( self ):
 32.1044 +
 32.1045 +        registry = self._makeOne()
 32.1046 +
 32.1047 +        self.assertEqual( len( registry.listProfiles() ), 0 )
 32.1048 +        self.assertEqual( len( registry.listProfiles() ), 0 )
 32.1049 +        self.assertRaises( KeyError, registry.getProfileInfo, 'nonesuch' )
 32.1050 +
 32.1051 +    def test_registerProfile_normal( self ):
 32.1052 +
 32.1053 +        NAME = 'one'
 32.1054 +        TITLE = 'One'
 32.1055 +        DESCRIPTION = 'One profile'
 32.1056 +        PATH = '/path/to/one'
 32.1057 +        PRODUCT = 'TestProduct'
 32.1058 +        PROFILE_TYPE = EXTENSION
 32.1059 +        PROFILE_ID = 'TestProduct:one'
 32.1060 +
 32.1061 +        registry = self._makeOne()
 32.1062 +        registry.registerProfile( NAME
 32.1063 +                                , TITLE
 32.1064 +                                , DESCRIPTION
 32.1065 +                                , PATH
 32.1066 +                                , PRODUCT
 32.1067 +                                , PROFILE_TYPE
 32.1068 +                                )
 32.1069 +
 32.1070 +        self.assertEqual( len( registry.listProfiles() ), 1 )
 32.1071 +        self.assertEqual( len( registry.listProfileInfo() ), 1 )
 32.1072 +
 32.1073 +        info = registry.getProfileInfo( PROFILE_ID )
 32.1074 +
 32.1075 +        self.assertEqual( info[ 'id' ], PROFILE_ID )
 32.1076 +        self.assertEqual( info[ 'title' ], TITLE )
 32.1077 +        self.assertEqual( info[ 'description' ], DESCRIPTION )
 32.1078 +        self.assertEqual( info[ 'path' ], PATH )
 32.1079 +        self.assertEqual( info[ 'product' ], PRODUCT )
 32.1080 +        self.assertEqual( info[ 'type' ], PROFILE_TYPE )
 32.1081 +
 32.1082 +    def test_registerProfile_duplicate( self ):
 32.1083 +
 32.1084 +        PROFILE_ID = 'one'
 32.1085 +        TITLE = 'One'
 32.1086 +        DESCRIPTION = 'One profile'
 32.1087 +        PATH = '/path/to/one'
 32.1088 +
 32.1089 +        registry = self._makeOne()
 32.1090 +        registry.registerProfile( PROFILE_ID, TITLE, DESCRIPTION, PATH )
 32.1091 +        self.assertRaises( KeyError
 32.1092 +                         , registry.registerProfile
 32.1093 +                         , PROFILE_ID, TITLE, DESCRIPTION, PATH )
 32.1094 +
 32.1095 +
 32.1096 +def test_suite():
 32.1097 +    return unittest.TestSuite((
 32.1098 +        unittest.makeSuite( ImportStepRegistryTests ),
 32.1099 +        unittest.makeSuite( ExportStepRegistryTests ),
 32.1100 +        unittest.makeSuite( ToolsetRegistryTests ),
 32.1101 +        unittest.makeSuite( ProfileRegistryTests ),
 32.1102 +        ))
 32.1103 +
 32.1104 +if __name__ == '__main__':
 32.1105 +    unittest.main(defaultTest='test_suite')
    33.1 new file mode 100644
    33.2 --- /dev/null
    33.3 +++ b/tests/test_rolemap.py
    33.4 @@ -0,0 +1,787 @@
    33.5 +##############################################################################
    33.6 +#
    33.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    33.8 +#
    33.9 +# This software is subject to the provisions of the Zope Public License,
   33.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   33.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   33.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   33.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   33.14 +# FOR A PARTICULAR PURPOSE.
   33.15 +#
   33.16 +##############################################################################
   33.17 +""" CMFSetup rolemap export / import unit tests
   33.18 +
   33.19 +$Id: test_rolemap.py 37135 2005-07-08 13:24:33Z tseaver $
   33.20 +"""
   33.21 +
   33.22 +import unittest
   33.23 +import Testing
   33.24 +try:
   33.25 +    import Zope2
   33.26 +except ImportError: # BBB: for Zope 2.7
   33.27 +    import Zope as Zope2
   33.28 +Zope2.startup()
   33.29 +
   33.30 +from OFS.Folder import Folder
   33.31 +
   33.32 +from common import BaseRegistryTests
   33.33 +from common import DummyExportContext
   33.34 +from common import DummyImportContext
   33.35 +
   33.36 +class RolemapConfiguratorTests( BaseRegistryTests ):
   33.37 +
   33.38 +    def _getTargetClass( self ):
   33.39 +
   33.40 +        from Products.CMFSetup.rolemap import RolemapConfigurator
   33.41 +        return RolemapConfigurator
   33.42 +
   33.43 +    def test_listRoles_normal( self ):
   33.44 +
   33.45 +        EXPECTED = [ 'Anonymous', 'Authenticated', 'Manager', 'Owner' ]
   33.46 +        self.root.site = Folder( id='site' )
   33.47 +        site = self.root.site
   33.48 +        configurator = self._makeOne( site )
   33.49 +
   33.50 +        roles = list( configurator.listRoles() )
   33.51 +        self.assertEqual( len( roles ), len( EXPECTED ) )
   33.52 +
   33.53 +        roles.sort()
   33.54 +
   33.55 +        for found, expected in zip( roles, EXPECTED ):
   33.56 +            self.assertEqual( found, expected )
   33.57 +
   33.58 +    def test_listRoles_added( self ):
   33.59 +
   33.60 +        EXPECTED = [ 'Anonymous', 'Authenticated', 'Manager', 'Owner', 'ZZZ' ]
   33.61 +        self.root.site = Folder( id='site' )
   33.62 +        site = self.root.site
   33.63 +        existing_roles = list( getattr( site, '__ac_roles__', [] ) )[:]
   33.64 +        existing_roles.append( 'ZZZ' )
   33.65 +        site.__ac_roles__ = existing_roles
   33.66 +
   33.67 +        configurator = self._makeOne( site )
   33.68 +
   33.69 +        roles = list( configurator.listRoles() )
   33.70 +        self.assertEqual( len( roles ), len( EXPECTED ) )
   33.71 +
   33.72 +        roles.sort()
   33.73 +
   33.74 +        for found, expected in zip( roles, EXPECTED ):
   33.75 +            self.assertEqual( found, expected )
   33.76 +
   33.77 +    def test_listPermissions_nooverrides( self ):
   33.78 +
   33.79 +        self.root.site = Folder( id='site' )
   33.80 +        site = self.root.site
   33.81 +        configurator = self._makeOne( site )
   33.82 +
   33.83 +        self.assertEqual( len( configurator.listPermissions() ), 0 )
   33.84 +
   33.85 +    def test_listPermissions_nooverrides( self ):
   33.86 +
   33.87 +        site = Folder( id='site' ).__of__( self.root )
   33.88 +        configurator = self._makeOne( site )
   33.89 +
   33.90 +        self.assertEqual( len( configurator.listPermissions() ), 0 )
   33.91 +
   33.92 +    def test_listPermissions_acquire( self ):
   33.93 +
   33.94 +        ACI = 'Access contents information'
   33.95 +        ROLES = [ 'Manager', 'Owner' ]
   33.96 +
   33.97 +        site = Folder( id='site' ).__of__( self.root )
   33.98 +        site.manage_permission( ACI, ROLES, acquire=1 )
   33.99 +        configurator = self._makeOne( site )
  33.100 +
  33.101 +        self.assertEqual( len( configurator.listPermissions() ), 1 )
  33.102 +        info = configurator.listPermissions()[ 0 ]
  33.103 +        self.assertEqual( info[ 'name' ], ACI )
  33.104 +        self.assertEqual( info[ 'roles' ], ROLES )
  33.105 +        self.failUnless( info[ 'acquire' ] )
  33.106 +
  33.107 +    def test_listPermissions_no_acquire( self ):
  33.108 +
  33.109 +        ACI = 'Access contents information'
  33.110 +        ROLES = [ 'Manager', 'Owner' ]
  33.111 +
  33.112 +        site = Folder( id='site' ).__of__( self.root )
  33.113 +        site.manage_permission( ACI, ROLES )
  33.114 +        configurator = self._makeOne( site )
  33.115 +
  33.116 +        self.assertEqual( len( configurator.listPermissions() ), 1 )
  33.117 +        info = configurator.listPermissions()[ 0 ]
  33.118 +        self.assertEqual( info[ 'name' ], ACI )
  33.119 +        self.assertEqual( info[ 'roles' ], ROLES )
  33.120 +        self.failIf( info[ 'acquire' ] )
  33.121 +
  33.122 +    def test_generateXML_empty( self ):
  33.123 +
  33.124 +        self.root.site = Folder( id='site' )
  33.125 +        site = self.root.site
  33.126 +        configurator = self._makeOne( site ).__of__( site )
  33.127 +
  33.128 +        self._compareDOM( configurator.generateXML(), _EMPTY_EXPORT )
  33.129 +
  33.130 +    def test_generateXML_added_role( self ):
  33.131 +
  33.132 +        self.root.site = Folder( id='site' )
  33.133 +        site = self.root.site
  33.134 +        existing_roles = list( getattr( site, '__ac_roles__', [] ) )[:]
  33.135 +        existing_roles.append( 'ZZZ' )
  33.136 +        site.__ac_roles__ = existing_roles
  33.137 +        configurator = self._makeOne( site ).__of__( site )
  33.138 +
  33.139 +        self._compareDOM( configurator.generateXML(), _ADDED_ROLE_EXPORT )
  33.140 +
  33.141 +    def test_generateEXML_acquired_perm( self ):
  33.142 +
  33.143 +        ACI = 'Access contents information'
  33.144 +        ROLES = [ 'Manager', 'Owner' ]
  33.145 +
  33.146 +        site = Folder( id='site' ).__of__( self.root )
  33.147 +        site.manage_permission( ACI, ROLES, acquire=1 )
  33.148 +        configurator = self._makeOne( site ).__of__( site )
  33.149 +
  33.150 +        self._compareDOM( configurator.generateXML(), _ACQUIRED_EXPORT )
  33.151 +
  33.152 +    def test_generateEXML_unacquired_perm( self ):
  33.153 +
  33.154 +        ACI = 'Access contents information'
  33.155 +        ROLES = [ 'Manager', 'Owner', 'ZZZ' ]
  33.156 +
  33.157 +        site = Folder( id='site' ).__of__( self.root )
  33.158 +        existing_roles = list( getattr( site, '__ac_roles__', [] ) )[:]
  33.159 +        existing_roles.append( 'ZZZ' )
  33.160 +        site.__ac_roles__ = existing_roles
  33.161 +        site.manage_permission( ACI, ROLES )
  33.162 +        configurator = self._makeOne( site ).__of__( site )
  33.163 +
  33.164 +        self._compareDOM( configurator.generateXML(), _COMBINED_EXPORT )
  33.165 +
  33.166 +    def test_generateEXML_unacquired_perm_added_role( self ):
  33.167 +
  33.168 +        ACI = 'Access contents information'
  33.169 +        ROLES = [ 'Manager', 'Owner' ]
  33.170 +
  33.171 +        site = Folder( id='site' ).__of__( self.root )
  33.172 +        site.manage_permission( ACI, ROLES )
  33.173 +        configurator = self._makeOne( site ).__of__( site )
  33.174 +
  33.175 +        self._compareDOM( configurator.generateXML(), _UNACQUIRED_EXPORT )
  33.176 +
  33.177 +    def test_parseXML_empty( self ):
  33.178 +
  33.179 +        self.root.site = Folder( id='site' )
  33.180 +        site = self.root.site
  33.181 +        existing_roles = list( getattr( site, '__ac_roles__', [] ) )[:]
  33.182 +        configurator = self._makeOne( site )
  33.183 +
  33.184 +        rolemap_info = configurator.parseXML( _EMPTY_EXPORT )
  33.185 +
  33.186 +        self.assertEqual( len( rolemap_info[ 'roles' ] ), 4 )
  33.187 +        self.assertEqual( len( rolemap_info[ 'permissions' ] ), 0 )
  33.188 +
  33.189 +    def test_parseXML_added_role( self ):
  33.190 +
  33.191 +        self.root.site = Folder( id='site' )
  33.192 +        site = self.root.site
  33.193 +        configurator = self._makeOne( site )
  33.194 +
  33.195 +        rolemap_info = configurator.parseXML( _ADDED_ROLE_EXPORT )
  33.196 +        roles = rolemap_info[ 'roles' ]
  33.197 +
  33.198 +        self.assertEqual( len( roles ), 5 )
  33.199 +        self.failUnless( 'Anonymous' in roles )
  33.200 +        self.failUnless( 'Authenticated' in roles )
  33.201 +        self.failUnless( 'Manager' in roles )
  33.202 +        self.failUnless( 'Owner' in roles )
  33.203 +        self.failUnless( 'ZZZ' in roles )
  33.204 +
  33.205 +    def test_parseXML_acquired_permission( self ):
  33.206 +
  33.207 +        ACI = 'Access contents information'
  33.208 +
  33.209 +        self.root.site = Folder( id='site' )
  33.210 +        site = self.root.site
  33.211 +        configurator = self._makeOne( site )
  33.212 +
  33.213 +        rolemap_info = configurator.parseXML( _ACQUIRED_EXPORT )
  33.214 +
  33.215 +        self.assertEqual( len( rolemap_info[ 'permissions' ] ), 1 )
  33.216 +        permission = rolemap_info[ 'permissions' ][ 0 ]
  33.217 +
  33.218 +        self.assertEqual( permission[ 'name' ], ACI )
  33.219 +        self.failUnless( permission[ 'acquire' ] )
  33.220 +
  33.221 +        p_roles = permission[ 'roles' ]
  33.222 +        self.assertEqual( len( p_roles ), 2 )
  33.223 +        self.failUnless( 'Manager' in p_roles )
  33.224 +        self.failUnless( 'Owner' in p_roles )
  33.225 +
  33.226 +    def test_parseXML_unacquired_permission( self ):
  33.227 +
  33.228 +        ACI = 'Access contents information'
  33.229 +
  33.230 +        self.root.site = Folder( id='site' )
  33.231 +        site = self.root.site
  33.232 +        configurator = self._makeOne( site )
  33.233 +
  33.234 +        rolemap_info = configurator.parseXML( _UNACQUIRED_EXPORT )
  33.235 +
  33.236 +        self.assertEqual( len( rolemap_info[ 'permissions' ] ), 1 )
  33.237 +        permission = rolemap_info[ 'permissions' ][ 0 ]
  33.238 +
  33.239 +        self.assertEqual( permission[ 'name' ], ACI )
  33.240 +        self.failIf( permission[ 'acquire' ] )
  33.241 +
  33.242 +        p_roles = permission[ 'roles' ]
  33.243 +        self.assertEqual( len( p_roles ), 2 )
  33.244 +        self.failUnless( 'Manager' in p_roles )
  33.245 +        self.failUnless( 'Owner' in p_roles )
  33.246 +
  33.247 +    def test_parseXML_unacquired_permission_added_role( self ):
  33.248 +
  33.249 +        ACI = 'Access contents information'
  33.250 +
  33.251 +        self.root.site = Folder( id='site' )
  33.252 +        site = self.root.site
  33.253 +        configurator = self._makeOne( site )
  33.254 +
  33.255 +        rolemap_info = configurator.parseXML( _COMBINED_EXPORT )
  33.256 +        roles = rolemap_info[ 'roles' ]
  33.257 +
  33.258 +        self.assertEqual( len( roles ), 5 )
  33.259 +        self.failUnless( 'Anonymous' in roles )
  33.260 +        self.failUnless( 'Authenticated' in roles )
  33.261 +        self.failUnless( 'Manager' in roles )
  33.262 +        self.failUnless( 'Owner' in roles )
  33.263 +        self.failUnless( 'ZZZ' in roles )
  33.264 +
  33.265 +        self.assertEqual( len( rolemap_info[ 'permissions' ] ), 1 )
  33.266 +        permission = rolemap_info[ 'permissions' ][ 0 ]
  33.267 +
  33.268 +        self.assertEqual( permission[ 'name' ], ACI )
  33.269 +        self.failIf( permission[ 'acquire' ] )
  33.270 +
  33.271 +        p_roles = permission[ 'roles' ]
  33.272 +        self.assertEqual( len( p_roles ), 3 )
  33.273 +        self.failUnless( 'Manager' in p_roles )
  33.274 +        self.failUnless( 'Owner' in p_roles )
  33.275 +        self.failUnless( 'ZZZ' in p_roles )
  33.276 +
  33.277 +
  33.278 +
  33.279 +_EMPTY_EXPORT = """\
  33.280 +<?xml version="1.0"?>
  33.281 +<rolemap>
  33.282 +  <roles>
  33.283 +    <role name="Anonymous"/>
  33.284 +    <role name="Authenticated"/>
  33.285 +    <role name="Manager"/>
  33.286 +    <role name="Owner"/>
  33.287 +  </roles>
  33.288 +  <permissions>
  33.289 +  </permissions>
  33.290 +</rolemap>
  33.291 +"""
  33.292 +
  33.293 +_ADDED_ROLE_EXPORT = """\
  33.294 +<?xml version="1.0"?>
  33.295 +<rolemap>
  33.296 +  <roles>
  33.297 +    <role name="Anonymous"/>
  33.298 +    <role name="Authenticated"/>
  33.299 +    <role name="Manager"/>
  33.300 +    <role name="Owner"/>
  33.301 +    <role name="ZZZ"/>
  33.302 +  </roles>
  33.303 +  <permissions>
  33.304 +  </permissions>
  33.305 +</rolemap>
  33.306 +"""
  33.307 +
  33.308 +_ACQUIRED_EXPORT = """\
  33.309 +<?xml version="1.0"?>
  33.310 +<rolemap>
  33.311 +  <roles>
  33.312 +    <role name="Anonymous"/>
  33.313 +    <role name="Authenticated"/>
  33.314 +    <role name="Manager"/>
  33.315 +    <role name="Owner"/>
  33.316 +  </roles>
  33.317 +  <permissions>
  33.318 +    <permission name="Access contents information"
  33.319 +                acquire="True">
  33.320 +      <role name="Manager"/>
  33.321 +      <role name="Owner"/>
  33.322 +    </permission>
  33.323 +  </permissions>
  33.324 +</rolemap>
  33.325 +"""
  33.326 +
  33.327 +_UNACQUIRED_EXPORT = """\
  33.328 +<?xml version="1.0"?>
  33.329 +<rolemap>
  33.330 +  <roles>
  33.331 +    <role name="Anonymous"/>
  33.332 +    <role name="Authenticated"/>
  33.333 +    <role name="Manager"/>
  33.334 +    <role name="Owner"/>
  33.335 +  </roles>
  33.336 +  <permissions>
  33.337 +    <permission name="Access contents information"
  33.338 +                acquire="False">
  33.339 +      <role name="Manager"/>
  33.340 +      <role name="Owner"/>
  33.341 +    </permission>
  33.342 +  </permissions>
  33.343 +</rolemap>
  33.344 +"""
  33.345 +
  33.346 +_COMBINED_EXPORT = """\
  33.347 +<?xml version="1.0"?>
  33.348 +<rolemap>
  33.349 +  <roles>
  33.350 +    <role name="Anonymous"/>
  33.351 +    <role name="Authenticated"/>
  33.352 +    <role name="Manager"/>
  33.353 +    <role name="Owner"/>
  33.354 +    <role name="ZZZ"/>
  33.355 +  </roles>
  33.356 +  <permissions>
  33.357 +    <permission name="Access contents information"
  33.358 +                acquire="False">
  33.359 +      <role name="Manager"/>
  33.360 +      <role name="Owner"/>
  33.361 +      <role name="ZZZ"/>
  33.362 +    </permission>
  33.363 +  </permissions>
  33.364 +</rolemap>
  33.365 +"""
  33.366 +
  33.367 +class Test_exportRolemap( BaseRegistryTests ):
  33.368 +
  33.369 +    def test_unchanged( self ):
  33.370 +
  33.371 +        self.root.site = Folder( 'site' )
  33.372 +        site = self.root.site
  33.373 +
  33.374 +        context = DummyExportContext( site )
  33.375 +
  33.376 +        from Products.CMFSetup.rolemap import exportRolemap
  33.377 +        exportRolemap( context )
  33.378 +
  33.379 +        self.assertEqual( len( context._wrote ), 1 )
  33.380 +        filename, text, content_type = context._wrote[ 0 ]
  33.381 +        self.assertEqual( filename, 'rolemap.xml' )
  33.382 +        self._compareDOM( text, _EMPTY_EXPORT )
  33.383 +        self.assertEqual( content_type, 'text/xml' )
  33.384 +
  33.385 +    def test_added_role( self ):
  33.386 +
  33.387 +        self.root.site = Folder( 'site' )
  33.388 +        site = self.root.site
  33.389 +        existing_roles = list( getattr( site, '__ac_roles__', [] ) )[:]
  33.390 +        existing_roles.append( 'ZZZ' )
  33.391 +        site.__ac_roles__ = existing_roles
  33.392 +
  33.393 +        context = DummyExportContext( site )
  33.394 +
  33.395 +        from Products.CMFSetup.rolemap import exportRolemap
  33.396 +        exportRolemap( context )
  33.397 +
  33.398 +        self.assertEqual( len( context._wrote ), 1 )
  33.399 +        filename, text, content_type = context._wrote[ 0 ]
  33.400 +        self.assertEqual( filename, 'rolemap.xml' )
  33.401 +        self._compareDOM( text, _ADDED_ROLE_EXPORT )
  33.402 +        self.assertEqual( content_type, 'text/xml' )
  33.403 +
  33.404 +
  33.405 +    def test_acquired_perm( self ):
  33.406 +
  33.407 +        ACI = 'Access contents information'
  33.408 +        ROLES = [ 'Manager', 'Owner' ]
  33.409 +
  33.410 +        self.root.site = Folder( 'site' )
  33.411 +        site = self.root.site
  33.412 +        site.manage_permission( ACI, ROLES, acquire=1 )
  33.413 +
  33.414 +        context = DummyExportContext( site )
  33.415 +
  33.416 +        from Products.CMFSetup.rolemap import exportRolemap
  33.417 +        exportRolemap( context )
  33.418 +
  33.419 +        self.assertEqual( len( context._wrote ), 1 )
  33.420 +        filename, text, content_type = context._wrote[ 0 ]
  33.421 +        self.assertEqual( filename, 'rolemap.xml' )
  33.422 +        self._compareDOM( text, _ACQUIRED_EXPORT )
  33.423 +        self.assertEqual( content_type, 'text/xml' )
  33.424 +
  33.425 +    def test_unacquired_perm( self ):
  33.426 +
  33.427 +        ACI = 'Access contents information'
  33.428 +        ROLES = [ 'Manager', 'Owner', 'ZZZ' ]
  33.429 +
  33.430 +        self.root.site = Folder( 'site' )
  33.431 +        site = self.root.site
  33.432 +        existing_roles = list( getattr( site, '__ac_roles__', [] ) )[:]
  33.433 +        existing_roles.append( 'ZZZ' )
  33.434 +        site.__ac_roles__ = existing_roles
  33.435 +        site.manage_permission( ACI, ROLES )
  33.436 +
  33.437 +        context = DummyExportContext( site )
  33.438 +
  33.439 +        from Products.CMFSetup.rolemap import exportRolemap
  33.440 +        exportRolemap( context )
  33.441 +
  33.442 +        self.assertEqual( len( context._wrote ), 1 )
  33.443 +        filename, text, content_type = context._wrote[ 0 ]
  33.444 +        self.assertEqual( filename, 'rolemap.xml' )
  33.445 +        self._compareDOM( text, _COMBINED_EXPORT )
  33.446 +        self.assertEqual( content_type, 'text/xml' )
  33.447 +
  33.448 +    def test_unacquired_perm_added_role( self ):
  33.449 +
  33.450 +        ACI = 'Access contents information'
  33.451 +        ROLES = [ 'Manager', 'Owner' ]
  33.452 +
  33.453 +        self.root.site = Folder( 'site' )
  33.454 +        site = self.root.site
  33.455 +        site.manage_permission( ACI, ROLES )
  33.456 +
  33.457 +        context = DummyExportContext( site )
  33.458 +
  33.459 +        from Products.CMFSetup.rolemap import exportRolemap
  33.460 +        exportRolemap( context )
  33.461 +
  33.462 +        self.assertEqual( len( context._wrote ), 1 )
  33.463 +        filename, text, content_type = context._wrote[ 0 ]
  33.464 +        self.assertEqual( filename, 'rolemap.xml' )
  33.465 +        self._compareDOM( text, _UNACQUIRED_EXPORT )
  33.466 +        self.assertEqual( content_type, 'text/xml' )
  33.467 +
  33.468 +class Test_importRolemap( BaseRegistryTests ):
  33.469 +
  33.470 +    def test_empty_default_purge( self ):
  33.471 +
  33.472 +        self.root.site = Folder( id='site' )
  33.473 +        site = self.root.site
  33.474 +        original_roles = list( getattr( site, '__ac_roles__', [] ) )[:]
  33.475 +        modified_roles = original_roles[:]
  33.476 +        modified_roles.append( 'ZZZ' )
  33.477 +        site.__ac_roles__ = modified_roles
  33.478 +
  33.479 +        context = DummyImportContext( site )
  33.480 +        context._files[ 'rolemap.xml' ] = _EMPTY_EXPORT
  33.481 +
  33.482 +        from Products.CMFSetup.rolemap import importRolemap
  33.483 +        importRolemap( context )
  33.484 +
  33.485 +        new_roles = list( getattr( site, '__ac_roles__', [] ) )[:]
  33.486 +
  33.487 +        original_roles.sort()
  33.488 +        new_roles.sort()
  33.489 +
  33.490 +        self.assertEqual( original_roles, new_roles )
  33.491 +
  33.492 +    def test_empty_explicit_purge( self ):
  33.493 +
  33.494 +        self.root.site = Folder( id='site' )
  33.495 +        site = self.root.site
  33.496 +        original_roles = list( getattr( site, '__ac_roles__', [] ) )[:]
  33.497 +        modified_roles = original_roles[:]
  33.498 +        modified_roles.append( 'ZZZ' )
  33.499 +        site.__ac_roles__ = modified_roles
  33.500 +
  33.501 +        context = DummyImportContext( site, True )
  33.502 +        context._files[ 'rolemap.xml' ] = _EMPTY_EXPORT
  33.503 +
  33.504 +        from Products.CMFSetup.rolemap import importRolemap
  33.505 +        importRolemap( context )
  33.506 +
  33.507 +        new_roles = list( getattr( site, '__ac_roles__', [] ) )[:]
  33.508 +
  33.509 +        original_roles.sort()
  33.510 +        new_roles.sort()
  33.511 +
  33.512 +        self.assertEqual( original_roles, new_roles )
  33.513 +
  33.514 +    def test_empty_skip_purge( self ):
  33.515 +
  33.516 +        self.root.site = Folder( id='site' )
  33.517 +        site = self.root.site
  33.518 +        original_roles = list( getattr( site, '__ac_roles__', [] ) )[:]
  33.519 +        modified_roles = original_roles[:]
  33.520 +        modified_roles.append( 'ZZZ' )
  33.521 +        site.__ac_roles__ = modified_roles
  33.522 +
  33.523 +        context = DummyImportContext( site, False )
  33.524 +        context._files[ 'rolemap.xml' ] = _EMPTY_EXPORT
  33.525 +
  33.526 +        from Products.CMFSetup.rolemap import importRolemap
  33.527 +        importRolemap( context )
  33.528 +
  33.529 +        new_roles = list( getattr( site, '__ac_roles__', [] ) )[:]
  33.530 +
  33.531 +        modified_roles.sort()
  33.532 +        new_roles.sort()
  33.533 +
  33.534 +        self.assertEqual( modified_roles, new_roles )
  33.535 +
  33.536 +    def test_acquired_permission_explicit_purge( self ):
  33.537 +
  33.538 +        ACI = 'Access contents information'
  33.539 +        VIEW = 'View'
  33.540 +
  33.541 +        self.root.site = Folder( id='site' )
  33.542 +        site = self.root.site
  33.543 +        site.manage_permission( ACI, () )
  33.544 +        site.manage_permission( VIEW, () )
  33.545 +
  33.546 +        existing_allowed = [ x[ 'name' ]
  33.547 +                                for x in site.rolesOfPermission( ACI )
  33.548 +                                if x[ 'selected' ] ]
  33.549 +
  33.550 +        self.assertEqual( existing_allowed, [] )
  33.551 +
  33.552 +        self.failIf( site.acquiredRolesAreUsedBy( ACI ) )
  33.553 +        self.failIf( site.acquiredRolesAreUsedBy( VIEW ) )
  33.554 +
  33.555 +        context = DummyImportContext( site, True )
  33.556 +        context._files[ 'rolemap.xml' ] = _ACQUIRED_EXPORT
  33.557 +
  33.558 +        from Products.CMFSetup.rolemap import importRolemap
  33.559 +        importRolemap( context )
  33.560 +
  33.561 +        new_allowed = [ x[ 'name' ]
  33.562 +                           for x in site.rolesOfPermission( ACI )
  33.563 +                           if x[ 'selected' ] ]
  33.564 +
  33.565 +        self.assertEqual( new_allowed, [ 'Manager', 'Owner' ] )
  33.566 +
  33.567 +        # ACI is overwritten by XML, but VIEW was purged
  33.568 +        self.failUnless( site.acquiredRolesAreUsedBy( ACI ) )
  33.569 +        self.failUnless( site.acquiredRolesAreUsedBy( VIEW ) )
  33.570 +
  33.571 +    def test_acquired_permission_no_purge( self ):
  33.572 +
  33.573 +        ACI = 'Access contents information'
  33.574 +        VIEW = 'View'
  33.575 +
  33.576 +        self.root.site = Folder( id='site' )
  33.577 +        site = self.root.site
  33.578 +        site.manage_permission( ACI, () )
  33.579 +        site.manage_permission( VIEW, () )
  33.580 +
  33.581 +        existing_allowed = [ x[ 'name' ]
  33.582 +                                for x in site.rolesOfPermission( ACI )
  33.583 +                                if x[ 'selected' ] ]
  33.584 +
  33.585 +        self.assertEqual( existing_allowed, [] )
  33.586 +
  33.587 +        self.failIf( site.acquiredRolesAreUsedBy( ACI ) )
  33.588 +
  33.589 +        context = DummyImportContext( site, False )
  33.590 +        context._files[ 'rolemap.xml' ] = _ACQUIRED_EXPORT
  33.591 +
  33.592 +        from Products.CMFSetup.rolemap import importRolemap
  33.593 +        importRolemap( context )
  33.594 +
  33.595 +        new_allowed = [ x[ 'name' ]
  33.596 +                           for x in site.rolesOfPermission( ACI )
  33.597 +                           if x[ 'selected' ] ]
  33.598 +
  33.599 +        self.assertEqual( new_allowed, [ 'Manager', 'Owner' ] )
  33.600 +
  33.601 +        # ACI is overwritten by XML, but VIEW is not
  33.602 +        self.failUnless( site.acquiredRolesAreUsedBy( ACI ) )
  33.603 +        self.failIf( site.acquiredRolesAreUsedBy( VIEW ) )
  33.604 +
  33.605 +    def test_unacquired_permission_explicit_purge( self ):
  33.606 +
  33.607 +        ACI = 'Access contents information'
  33.608 +        VIEW = 'View'
  33.609 +
  33.610 +        self.root.site = Folder( id='site' )
  33.611 +        site = self.root.site
  33.612 +        site.manage_permission( VIEW, () )
  33.613 +
  33.614 +        existing_allowed = [ x[ 'name' ]
  33.615 +                                for x in site.rolesOfPermission( ACI )
  33.616 +                                if x[ 'selected' ] ]
  33.617 +
  33.618 +        self.assertEqual( existing_allowed, [ 'Manager' ] )
  33.619 +
  33.620 +        self.failUnless( site.acquiredRolesAreUsedBy( ACI ) )
  33.621 +        self.failIf( site.acquiredRolesAreUsedBy( VIEW ) )
  33.622 +
  33.623 +        context = DummyImportContext( site, True )
  33.624 +        context._files[ 'rolemap.xml' ] = _UNACQUIRED_EXPORT
  33.625 +
  33.626 +        from Products.CMFSetup.rolemap import importRolemap
  33.627 +        importRolemap( context )
  33.628 +
  33.629 +        new_allowed = [ x[ 'name' ]
  33.630 +                           for x in site.rolesOfPermission( ACI )
  33.631 +                           if x[ 'selected' ] ]
  33.632 +
  33.633 +        self.assertEqual( new_allowed, [ 'Manager', 'Owner' ] )
  33.634 +
  33.635 +        self.failIf( site.acquiredRolesAreUsedBy( ACI ) )
  33.636 +        self.failUnless( site.acquiredRolesAreUsedBy( VIEW ) )
  33.637 +
  33.638 +    def test_unacquired_permission_skip_purge( self ):
  33.639 +
  33.640 +        ACI = 'Access contents information'
  33.641 +        VIEW = 'View'
  33.642 +
  33.643 +        self.root.site = Folder( id='site' )
  33.644 +        site = self.root.site
  33.645 +        site.manage_permission( VIEW, () )
  33.646 +
  33.647 +        existing_allowed = [ x[ 'name' ]
  33.648 +                                for x in site.rolesOfPermission( ACI )
  33.649 +                                if x[ 'selected' ] ]
  33.650 +
  33.651 +        self.assertEqual( existing_allowed, [ 'Manager' ] )
  33.652 +
  33.653 +        self.failUnless( site.acquiredRolesAreUsedBy( ACI ) )
  33.654 +        self.failIf( site.acquiredRolesAreUsedBy( VIEW ) )
  33.655 +
  33.656 +        context = DummyImportContext( site, False )
  33.657 +        context._files[ 'rolemap.xml' ] = _UNACQUIRED_EXPORT
  33.658 +
  33.659 +        from Products.CMFSetup.rolemap import importRolemap
  33.660 +        importRolemap( context )
  33.661 +
  33.662 +        new_allowed = [ x[ 'name' ]
  33.663 +                           for x in site.rolesOfPermission( ACI )
  33.664 +                           if x[ 'selected' ] ]
  33.665 +
  33.666 +        self.assertEqual( new_allowed, [ 'Manager', 'Owner' ] )
  33.667 +
  33.668 +        self.failIf( site.acquiredRolesAreUsedBy( ACI ) )
  33.669 +        self.failIf( site.acquiredRolesAreUsedBy( VIEW ) )
  33.670 +
  33.671 +    def test_unacquired_permission_added_role_explicit_purge( self ):
  33.672 +
  33.673 +        ACI = 'Access contents information'
  33.674 +        VIEW = 'View'
  33.675 +
  33.676 +        self.root.site = Folder( id='site' )
  33.677 +        site = self.root.site
  33.678 +        site.manage_permission( VIEW, () )
  33.679 +
  33.680 +        existing_allowed = [ x[ 'name' ]
  33.681 +                                for x in site.rolesOfPermission( ACI )
  33.682 +                                if x[ 'selected' ] ]
  33.683 +
  33.684 +        self.assertEqual( existing_allowed, [ 'Manager' ] )
  33.685 +
  33.686 +        self.failUnless( site.acquiredRolesAreUsedBy( ACI ) )
  33.687 +        self.failIf( site.acquiredRolesAreUsedBy( VIEW ) )
  33.688 +
  33.689 +        self.failIf( site._has_user_defined_role( 'ZZZ' ) )
  33.690 +
  33.691 +        context = DummyImportContext( site, True )
  33.692 +        context._files[ 'rolemap.xml' ] = _COMBINED_EXPORT
  33.693 +
  33.694 +        from Products.CMFSetup.rolemap import importRolemap
  33.695 +        importRolemap( context )
  33.696 +
  33.697 +        self.failUnless( site._has_user_defined_role( 'ZZZ' ) )
  33.698 +
  33.699 +        new_allowed = [ x[ 'name' ]
  33.700 +                           for x in site.rolesOfPermission( ACI )
  33.701 +                           if x[ 'selected' ] ]
  33.702 +
  33.703 +        self.assertEqual( new_allowed, [ 'Manager', 'Owner', 'ZZZ' ] )
  33.704 +
  33.705 +        self.failIf( site.acquiredRolesAreUsedBy( ACI ) )
  33.706 +        self.failUnless( site.acquiredRolesAreUsedBy( VIEW ) )
  33.707 +
  33.708 +    def test_unacquired_permission_added_role_skip_purge( self ):
  33.709 +
  33.710 +        ACI = 'Access contents information'
  33.711 +        VIEW = 'View'
  33.712 +
  33.713 +        self.root.site = Folder( id='site' )
  33.714 +        site = self.root.site
  33.715 +        site.manage_permission( VIEW, () )
  33.716 +
  33.717 +        existing_allowed = [ x[ 'name' ]
  33.718 +                                for x in site.rolesOfPermission( ACI )
  33.719 +                                if x[ 'selected' ] ]
  33.720 +
  33.721 +        self.assertEqual( existing_allowed, [ 'Manager' ] )
  33.722 +
  33.723 +        self.failUnless( site.acquiredRolesAreUsedBy( ACI ) )
  33.724 +        self.failIf( site.acquiredRolesAreUsedBy( VIEW ) )
  33.725 +
  33.726 +        self.failIf( site._has_user_defined_role( 'ZZZ' ) )
  33.727 +
  33.728 +        context = DummyImportContext( site, False )
  33.729 +        context._files[ 'rolemap.xml' ] = _COMBINED_EXPORT
  33.730 +
  33.731 +        from Products.CMFSetup.rolemap import importRolemap
  33.732 +        importRolemap( context )
  33.733 +
  33.734 +        self.failUnless( site._has_user_defined_role( 'ZZZ' ) )
  33.735 +
  33.736 +        new_allowed = [ x[ 'name' ]
  33.737 +                           for x in site.rolesOfPermission( ACI )
  33.738 +                           if x[ 'selected' ] ]
  33.739 +
  33.740 +        self.assertEqual( new_allowed, [ 'Manager', 'Owner', 'ZZZ' ] )
  33.741 +
  33.742 +        self.failIf( site.acquiredRolesAreUsedBy( ACI ) )
  33.743 +        self.failIf( site.acquiredRolesAreUsedBy( VIEW ) )
  33.744 +
  33.745 +    def test_unacquired_permission_added_role_skip_purge_encode_ascii( self ):
  33.746 +
  33.747 +        ACI = 'Access contents information'
  33.748 +        VIEW = 'View'
  33.749 +
  33.750 +        self.root.site = Folder( id='site' )
  33.751 +        site = self.root.site
  33.752 +        site.manage_permission( VIEW, () )
  33.753 +
  33.754 +        existing_allowed = [ x[ 'name' ]
  33.755 +                                for x in site.rolesOfPermission( ACI )
  33.756 +                                if x[ 'selected' ] ]
  33.757 +
  33.758 +        self.assertEqual( existing_allowed, [ 'Manager' ] )
  33.759 +
  33.760 +        self.failUnless( site.acquiredRolesAreUsedBy( ACI ) )
  33.761 +        self.failIf( site.acquiredRolesAreUsedBy( VIEW ) )
  33.762 +
  33.763 +        self.failIf( site._has_user_defined_role( 'ZZZ' ) )
  33.764 +
  33.765 +        context = DummyImportContext( site, False, encoding='ascii' )
  33.766 +        context._files[ 'rolemap.xml' ] = _COMBINED_EXPORT
  33.767 +
  33.768 +        from Products.CMFSetup.rolemap import importRolemap
  33.769 +        importRolemap( context )
  33.770 +
  33.771 +        self.failUnless( site._has_user_defined_role( 'ZZZ' ) )
  33.772 +
  33.773 +        new_allowed = [ x[ 'name' ]
  33.774 +                           for x in site.rolesOfPermission( ACI )
  33.775 +                           if x[ 'selected' ] ]
  33.776 +
  33.777 +        self.assertEqual( new_allowed, [ 'Manager', 'Owner', 'ZZZ' ] )
  33.778 +
  33.779 +        self.failIf( site.acquiredRolesAreUsedBy( ACI ) )
  33.780 +        self.failIf( site.acquiredRolesAreUsedBy( VIEW ) )
  33.781 +
  33.782 +
  33.783 +def test_suite():
  33.784 +    return unittest.TestSuite((
  33.785 +        unittest.makeSuite( RolemapConfiguratorTests ),
  33.786 +        unittest.makeSuite( Test_exportRolemap ),
  33.787 +        unittest.makeSuite( Test_importRolemap ),
  33.788 +        ))
  33.789 +
  33.790 +if __name__ == '__main__':
  33.791 +    unittest.main(defaultTest='test_suite')
    34.1 new file mode 100644
    34.2 --- /dev/null
    34.3 +++ b/tests/test_skins.py
    34.4 @@ -0,0 +1,648 @@
    34.5 +##############################################################################
    34.6 +#
    34.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    34.8 +#
    34.9 +# This software is subject to the provisions of the Zope Public License,
   34.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   34.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   34.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   34.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   34.14 +# FOR A PARTICULAR PURPOSE.
   34.15 +#
   34.16 +##############################################################################
   34.17 +""" Unit tests for CMFSetup skins configurator
   34.18 +
   34.19 +$Id: test_skins.py 37135 2005-07-08 13:24:33Z tseaver $
   34.20 +"""
   34.21 +
   34.22 +import unittest
   34.23 +import Testing
   34.24 +try:
   34.25 +    import Zope2
   34.26 +except ImportError: # BBB: for Zope 2.7
   34.27 +    import Zope as Zope2
   34.28 +Zope2.startup()
   34.29 +
   34.30 +import os
   34.31 +
   34.32 +from OFS.Folder import Folder
   34.33 +from OFS.SimpleItem import Item
   34.34 +
   34.35 +from Products.CMFCore import DirectoryView
   34.36 +from Products.CMFCore.utils import expandpath
   34.37 +from Products.CMFCore.utils import minimalpath
   34.38 +
   34.39 +from common import BaseRegistryTests
   34.40 +from common import DOMComparator
   34.41 +from common import DummyExportContext
   34.42 +from common import DummyImportContext
   34.43 +
   34.44 +_TESTS_PATH = os.path.split( __file__ )[ 0 ]
   34.45 +
   34.46 +
   34.47 +class DummySite(Folder):
   34.48 +    _skin_setup_called = False
   34.49 +    def clearCurrentSkin(self):
   34.50 +        pass
   34.51 +    def setupCurrentSkin(self, REQUEST):
   34.52 +        self._skin_setup_called = True
   34.53 +
   34.54 +class DummySkinsTool( Folder ):
   34.55 +
   34.56 +    default_skin = 'default_skin'
   34.57 +    request_varname = 'request_varname'
   34.58 +    allow_any = False
   34.59 +    cookie_persistence = False
   34.60 +
   34.61 +    def __init__( self, selections={}, fsdvs=[] ):
   34.62 +
   34.63 +        self._selections = selections
   34.64 +
   34.65 +        for id, obj in fsdvs:
   34.66 +            self._setObject( id, obj )
   34.67 +
   34.68 +    def _getSelections( self ):
   34.69 +
   34.70 +        return self._selections
   34.71 +
   34.72 +    def getSkinPaths( self ):
   34.73 +
   34.74 +        result = list( self._selections.items() )
   34.75 +        result.sort()
   34.76 +        return result
   34.77 +
   34.78 +    def addSkinSelection( self, skinname, skinpath, test=0, make_default=0 ):
   34.79 +
   34.80 +        self._selections[ skinname ] = skinpath
   34.81 +
   34.82 +class DummyFSDV( Item ):
   34.83 +
   34.84 +    meta_type = DirectoryView.DirectoryView.meta_type
   34.85 +
   34.86 +    def __init__( self, id ):
   34.87 +
   34.88 +        self.id = id
   34.89 +        self._dirpath = minimalpath( os.path.join(_TESTS_PATH, id) )
   34.90 +
   34.91 +class _SkinsSetup( BaseRegistryTests ):
   34.92 +
   34.93 +    def setUp( self ):
   34.94 +        BaseRegistryTests.setUp( self )
   34.95 +        self._olddirreg = DirectoryView._dirreg
   34.96 +        self._dirreg = DirectoryView._dirreg = DirectoryView.DirectoryRegistry()
   34.97 +
   34.98 +    def tearDown( self ):
   34.99 +        DirectoryView._dirreg = self._olddirreg
  34.100 +        BaseRegistryTests.tearDown( self )
  34.101 +
  34.102 +    def _initSite( self, selections=None, fsdvs=None ):
  34.103 +
  34.104 +        if selections is None:
  34.105 +            selections = {}
  34.106 +
  34.107 +        if fsdvs is None:
  34.108 +            fsdvs = []
  34.109 +
  34.110 +        self.root.site = DummySite()
  34.111 +
  34.112 +        for id, fsdv in fsdvs:
  34.113 +            self._registerDirectoryView( expandpath(fsdv._dirpath) )
  34.114 +
  34.115 +        self.root.site.portal_skins = DummySkinsTool( selections, fsdvs )
  34.116 +
  34.117 +        return self.root.site
  34.118 +
  34.119 +    def _registerDirectoryView( self, dirpath, subdirs=0 ):
  34.120 +
  34.121 +        self._dirreg.registerDirectoryByPath( dirpath, subdirs )
  34.122 +
  34.123 +class SkinsToolConfiguratorTests( _SkinsSetup ):
  34.124 +
  34.125 +    def _getTargetClass( self ):
  34.126 +
  34.127 +        from Products.CMFSetup.skins import SkinsToolConfigurator
  34.128 +        return SkinsToolConfigurator
  34.129 +
  34.130 +    def test_empty( self ):
  34.131 +
  34.132 +        site = self._initSite()
  34.133 +        configurator = self._makeOne( site ).__of__( site )
  34.134 +
  34.135 +        self.assertEqual( len( configurator.listSkinPaths() ), 0 )
  34.136 +        self.assertEqual( len( configurator.listFSDirectoryViews() ), 0 )
  34.137 +
  34.138 +    def test_listSkinPaths( self ):
  34.139 +
  34.140 +        _PATHS = { 'basic' : 'one'
  34.141 +                 , 'fancy' : 'three, two, one'
  34.142 +                 }
  34.143 +
  34.144 +        site = self._initSite( selections=_PATHS )
  34.145 +        configurator = self._makeOne( site ).__of__( site )
  34.146 +
  34.147 +        self.assertEqual( len( configurator.listSkinPaths() ), 2 )
  34.148 +        info_list = configurator.listSkinPaths()
  34.149 +
  34.150 +        self.assertEqual( info_list[ 0 ][ 'id' ], 'basic' )
  34.151 +        self.assertEqual( info_list[ 0 ][ 'path' ]
  34.152 +                        , _PATHS[ 'basic' ].split( ', ' ) )
  34.153 +
  34.154 +        self.assertEqual( info_list[ 1 ][ 'id' ], 'fancy' )
  34.155 +        self.assertEqual( info_list[ 1 ][ 'path' ]
  34.156 +                        , _PATHS[ 'fancy' ].split( ', ' ) )
  34.157 +
  34.158 +    def test_listFSDirectoryViews( self ):
  34.159 +
  34.160 +        _IDS = ( 'one', 'two', 'three' )
  34.161 +        _FSDVS = [ ( id, DummyFSDV( id ) ) for id in _IDS ]
  34.162 +        site = self._initSite( fsdvs=_FSDVS )
  34.163 +        configurator = self._makeOne( site ).__of__( site )
  34.164 +
  34.165 +        info_list = configurator.listFSDirectoryViews()
  34.166 +        self.assertEqual( len( info_list ), len( _IDS ) )
  34.167 +
  34.168 +        ids = list( _IDS )
  34.169 +        ids.sort()
  34.170 +
  34.171 +        for i in range( len( ids ) ):
  34.172 +            self.assertEqual( info_list[ i ][ 'id' ], ids[ i ] )
  34.173 +            self.assertEqual( info_list[ i ][ 'directory' ]
  34.174 +                            , 'CMFSetup/tests/%s' % ids[ i ]
  34.175 +                            )
  34.176 +
  34.177 +    def test_generateXML_empty( self ):
  34.178 +
  34.179 +        site = self._initSite()
  34.180 +        configurator = self._makeOne( site ).__of__( site )
  34.181 +
  34.182 +        self._compareDOM( configurator.generateXML(), _EMPTY_EXPORT )
  34.183 +
  34.184 +    def test_generateXML_normal( self ):
  34.185 +
  34.186 +        _IDS = ( 'one', 'two', 'three' )
  34.187 +        _FSDVS = [ ( id, DummyFSDV( id ) ) for id in _IDS ]
  34.188 +        _PATHS = { 'basic' : 'one'
  34.189 +                 , 'fancy' : 'three, two, one'
  34.190 +                 }
  34.191 +
  34.192 +        site = self._initSite( selections=_PATHS, fsdvs=_FSDVS )
  34.193 +        tool = site.portal_skins
  34.194 +        tool.default_skin = 'basic'
  34.195 +        tool.request_varname = 'skin_var'
  34.196 +        tool.allow_any = True
  34.197 +        tool.cookie_persistence = True
  34.198 +
  34.199 +        configurator = self._makeOne( site ).__of__( site )
  34.200 +
  34.201 +        self._compareDOM( configurator.generateXML(), _NORMAL_EXPORT )
  34.202 +
  34.203 +    def test_parseXML_empty( self ):
  34.204 +
  34.205 +        _IDS = ( 'one', 'two', 'three' )
  34.206 +        _FSDVS = [ ( id, DummyFSDV( id ) ) for id in _IDS ]
  34.207 +        _PATHS = { 'basic' : 'one'
  34.208 +                 , 'fancy' : 'three, two, one'
  34.209 +                 }
  34.210 +
  34.211 +        site = self._initSite( selections=_PATHS, fsdvs=_FSDVS )
  34.212 +        skins_tool = site.portal_skins
  34.213 +
  34.214 +        self.failIf( site._skin_setup_called )
  34.215 +        self.assertEqual( len( skins_tool.getSkinPaths() ), 2 )
  34.216 +        self.assertEqual( len( skins_tool.objectItems() ), 3 )
  34.217 +
  34.218 +        configurator = self._makeOne( site ).__of__( site )
  34.219 +        tool_info = configurator.parseXML( _EMPTY_EXPORT )
  34.220 +
  34.221 +        self.assertEqual( tool_info[ 'default_skin' ], "default_skin" )
  34.222 +        self.assertEqual( tool_info[ 'request_varname' ], "request_varname" )
  34.223 +        self.failIf( tool_info[ 'allow_any' ] )
  34.224 +        self.failIf( tool_info[ 'cookie_persistence' ] )
  34.225 +        self.assertEqual( len( tool_info[ 'skin_dirs' ] ), 0 )
  34.226 +        self.assertEqual( len( tool_info[ 'skin_paths' ] ), 0 )
  34.227 +
  34.228 +    def test_parseXML_normal( self ):
  34.229 +
  34.230 +        site = self._initSite()
  34.231 +        self._registerDirectoryView( os.path.join( _TESTS_PATH, 'one' ) )
  34.232 +        self._registerDirectoryView( os.path.join( _TESTS_PATH, 'two' ) )
  34.233 +        self._registerDirectoryView( os.path.join( _TESTS_PATH, 'three' ) )
  34.234 +        skins_tool = site.portal_skins
  34.235 +
  34.236 +        self.failIf( site._skin_setup_called )
  34.237 +        self.assertEqual( len( skins_tool.getSkinPaths() ), 0 )
  34.238 +        self.assertEqual( len( skins_tool.objectItems() ), 0 )
  34.239 +
  34.240 +        configurator = self._makeOne( site ).__of__( site )
  34.241 +        tool_info = configurator.parseXML( _NORMAL_EXPORT )
  34.242 +
  34.243 +        self.assertEqual( tool_info[ 'default_skin' ], "basic" )
  34.244 +        self.assertEqual( tool_info[ 'request_varname' ], "skin_var" )
  34.245 +        self.failUnless( tool_info[ 'allow_any' ] )
  34.246 +        self.failUnless( tool_info[ 'cookie_persistence' ] )
  34.247 +        self.assertEqual( len( tool_info[ 'skin_dirs' ] ), 3 )
  34.248 +        self.assertEqual( len( tool_info[ 'skin_paths' ] ), 2 )
  34.249 +
  34.250 +
  34.251 +
  34.252 +_EMPTY_EXPORT = """\
  34.253 +<?xml version="1.0"?>
  34.254 +<skins-tool default_skin="default_skin"
  34.255 +            request_varname="request_varname"
  34.256 +            allow_any="False"
  34.257 +            cookie_persistence="False">
  34.258 +</skins-tool>
  34.259 +"""
  34.260 +
  34.261 +_NORMAL_EXPORT = """\
  34.262 +<?xml version="1.0"?>
  34.263 +<skins-tool default_skin="basic"
  34.264 +            request_varname="skin_var"
  34.265 +            allow_any="True"
  34.266 +            cookie_persistence="True">
  34.267 + <skin-directory id="one" directory="CMFSetup/tests/one" />
  34.268 + <skin-directory id="three" directory="CMFSetup/tests/three" />
  34.269 + <skin-directory id="two" directory="CMFSetup/tests/two" />
  34.270 + <skin-path id="basic">
  34.271 +  <layer name="one" />
  34.272 + </skin-path>
  34.273 + <skin-path id="fancy">
  34.274 +  <layer name="three" />
  34.275 +  <layer name="two" />
  34.276 +  <layer name="one" />
  34.277 + </skin-path>
  34.278 +</skins-tool>
  34.279 +"""
  34.280 +
  34.281 +_FRAGMENT_IMPORT = """\
  34.282 +<?xml version="1.0"?>
  34.283 +<skins-tool>
  34.284 + <skin-directory id="three" directory="CMFSetup/tests/three" />
  34.285 + <skin-path id="*">
  34.286 +  <layer name="three" insert-before="two"/>
  34.287 + </skin-path>
  34.288 +</skins-tool>
  34.289 +"""
  34.290 +
  34.291 +_FRAGMENT2_IMPORT = """\
  34.292 +<?xml version="1.0"?>
  34.293 +<skins-tool>
  34.294 + <skin-directory id="four" directory="CMFSetup/tests/four" />
  34.295 + <skin-path id="*">
  34.296 +  <layer name="four" insert-after="three"/>
  34.297 + </skin-path>
  34.298 +</skins-tool>
  34.299 +"""
  34.300 +
  34.301 +_FRAGMENT3_IMPORT = """\
  34.302 +<?xml version="1.0"?>
  34.303 +<skins-tool>
  34.304 + <skin-directory id="three" directory="CMFSetup/tests/three" />
  34.305 + <skin-directory id="four" directory="CMFSetup/tests/four" />
  34.306 + <skin-path id="*">
  34.307 +  <layer name="three" insert-before="*"/>
  34.308 +  <layer name="four" insert-after="*"/>
  34.309 + </skin-path>
  34.310 +</skins-tool>
  34.311 +"""
  34.312 +
  34.313 +_FRAGMENT4_IMPORT = """\
  34.314 +<?xml version="1.0"?>
  34.315 +<skins-tool>
  34.316 + <skin-path id="*">
  34.317 +  <layer name="three" remove="1"/>
  34.318 + </skin-path>
  34.319 +</skins-tool>
  34.320 +"""
  34.321 +
  34.322 +
  34.323 +class Test_exportSkinsTool( _SkinsSetup ):
  34.324 +
  34.325 +    def test_empty( self ):
  34.326 +
  34.327 +        site = self._initSite()
  34.328 +        context = DummyExportContext( site )
  34.329 +
  34.330 +        from Products.CMFSetup.skins import exportSkinsTool
  34.331 +        exportSkinsTool( context )
  34.332 +
  34.333 +        self.assertEqual( len( context._wrote ), 1 )
  34.334 +        filename, text, content_type = context._wrote[ 0 ]
  34.335 +        self.assertEqual( filename, 'skins.xml' )
  34.336 +        self._compareDOM( text, _EMPTY_EXPORT )
  34.337 +        self.assertEqual( content_type, 'text/xml' )
  34.338 +
  34.339 +    def test_normal( self ):
  34.340 +
  34.341 +        _IDS = ( 'one', 'two', 'three' )
  34.342 +        _FSDVS = [ ( id, DummyFSDV( id ) ) for id in _IDS ]
  34.343 +        _PATHS = { 'basic' : 'one'
  34.344 +                 , 'fancy' : 'three, two, one'
  34.345 +                 }
  34.346 +
  34.347 +        site = self._initSite( selections=_PATHS, fsdvs=_FSDVS )
  34.348 +        tool = site.portal_skins
  34.349 +        tool.default_skin = 'basic'
  34.350 +        tool.request_varname = 'skin_var'
  34.351 +        tool.allow_any = True
  34.352 +        tool.cookie_persistence = True
  34.353 +
  34.354 +        context = DummyExportContext( site )
  34.355 +
  34.356 +        from Products.CMFSetup.skins import exportSkinsTool
  34.357 +        exportSkinsTool( context )
  34.358 +
  34.359 +        self.assertEqual( len( context._wrote ), 1 )
  34.360 +        filename, text, content_type = context._wrote[ 0 ]
  34.361 +        self.assertEqual( filename, 'skins.xml' )
  34.362 +        self._compareDOM( text, _NORMAL_EXPORT )
  34.363 +        self.assertEqual( content_type, 'text/xml' )
  34.364 +
  34.365 +class Test_importSkinsTool( _SkinsSetup ):
  34.366 +
  34.367 +    def test_empty_default_purge( self ):
  34.368 +
  34.369 +        _IDS = ( 'one', 'two', 'three' )
  34.370 +        _FSDVS = [ ( id, DummyFSDV( id ) ) for id in _IDS ]
  34.371 +        _PATHS = { 'basic' : 'one'
  34.372 +                 , 'fancy' : 'three, two, one'
  34.373 +                 }
  34.374 +
  34.375 +        site = self._initSite( selections=_PATHS, fsdvs=_FSDVS )
  34.376 +        skins_tool = site.portal_skins
  34.377 +
  34.378 +        self.failIf( site._skin_setup_called )
  34.379 +        self.assertEqual( len( skins_tool.getSkinPaths() ), 2 )
  34.380 +        self.assertEqual( len( skins_tool.objectItems() ), 3 )
  34.381 +
  34.382 +        context = DummyImportContext( site )
  34.383 +        context._files[ 'skins.xml' ] = _EMPTY_EXPORT
  34.384 +
  34.385 +        from Products.CMFSetup.skins import importSkinsTool
  34.386 +        importSkinsTool( context )
  34.387 +
  34.388 +        self.assertEqual( skins_tool.default_skin, "default_skin" )
  34.389 +        self.assertEqual( skins_tool.request_varname, "request_varname" )
  34.390 +        self.failIf( skins_tool.allow_any )
  34.391 +        self.failIf( skins_tool.cookie_persistence )
  34.392 +
  34.393 +        self.failUnless( site._skin_setup_called )
  34.394 +        self.assertEqual( len( skins_tool.getSkinPaths() ), 0 )
  34.395 +        self.assertEqual( len( skins_tool.objectItems() ), 0 )
  34.396 +
  34.397 +    def test_empty_explicit_purge( self ):
  34.398 +
  34.399 +        _IDS = ( 'one', 'two', 'three' )
  34.400 +        _FSDVS = [ ( id, DummyFSDV( id ) ) for id in _IDS ]
  34.401 +        _PATHS = { 'basic' : 'one'
  34.402 +                 , 'fancy' : 'three, two, one'
  34.403 +                 }
  34.404 +
  34.405 +        site = self._initSite( selections=_PATHS, fsdvs=_FSDVS )
  34.406 +        skins_tool = site.portal_skins
  34.407 +
  34.408 +        self.failIf( site._skin_setup_called )
  34.409 +        self.assertEqual( len( skins_tool.getSkinPaths() ), 2 )
  34.410 +        self.assertEqual( len( skins_tool.objectItems() ), 3 )
  34.411 +
  34.412 +        context = DummyImportContext( site, True )
  34.413 +        context._files[ 'skins.xml' ] = _EMPTY_EXPORT
  34.414 +
  34.415 +        from Products.CMFSetup.skins import importSkinsTool
  34.416 +        importSkinsTool( context )
  34.417 +
  34.418 +        self.assertEqual( skins_tool.default_skin, "default_skin" )
  34.419 +        self.assertEqual( skins_tool.request_varname, "request_varname" )
  34.420 +        self.failIf( skins_tool.allow_any )
  34.421 +        self.failIf( skins_tool.cookie_persistence )
  34.422 +
  34.423 +        self.failUnless( site._skin_setup_called )
  34.424 +        self.assertEqual( len( skins_tool.getSkinPaths() ), 0 )
  34.425 +        self.assertEqual( len( skins_tool.objectItems() ), 0 )
  34.426 +
  34.427 +    def test_empty_skip_purge( self ):
  34.428 +
  34.429 +        _IDS = ( 'one', 'two', 'three' )
  34.430 +        _FSDVS = [ ( id, DummyFSDV( id ) ) for id in _IDS ]
  34.431 +        _PATHS = { 'basic' : 'one'
  34.432 +                 , 'fancy' : 'three, two, one'
  34.433 +                 }
  34.434 +
  34.435 +        site = self._initSite( selections=_PATHS, fsdvs=_FSDVS )
  34.436 +        skins_tool = site.portal_skins
  34.437 +
  34.438 +        self.failIf( site._skin_setup_called )
  34.439 +        self.assertEqual( len( skins_tool.getSkinPaths() ), 2 )
  34.440 +        self.assertEqual( len( skins_tool.objectItems() ), 3 )
  34.441 +
  34.442 +        context = DummyImportContext( site, False )
  34.443 +        context._files[ 'skins.xml' ] = _EMPTY_EXPORT
  34.444 +
  34.445 +        from Products.CMFSetup.skins import importSkinsTool
  34.446 +        importSkinsTool( context )
  34.447 +
  34.448 +        self.assertEqual( skins_tool.default_skin, "default_skin" )
  34.449 +        self.assertEqual( skins_tool.request_varname, "request_varname" )
  34.450 +        self.failIf( skins_tool.allow_any )
  34.451 +        self.failIf( skins_tool.cookie_persistence )
  34.452 +
  34.453 +        self.failUnless( site._skin_setup_called )
  34.454 +        self.assertEqual( len( skins_tool.getSkinPaths() ), 2 )
  34.455 +        self.assertEqual( len( skins_tool.objectItems() ), 3 )
  34.456 +
  34.457 +    def test_normal( self ):
  34.458 +
  34.459 +        site = self._initSite()
  34.460 +        self._registerDirectoryView( os.path.join( _TESTS_PATH, 'one' ) )
  34.461 +        self._registerDirectoryView( os.path.join( _TESTS_PATH, 'two' ) )
  34.462 +        self._registerDirectoryView( os.path.join( _TESTS_PATH, 'three' ) )
  34.463 +        skins_tool = site.portal_skins
  34.464 +
  34.465 +        self.failIf( site._skin_setup_called )
  34.466 +        self.assertEqual( len( skins_tool.getSkinPaths() ), 0 )
  34.467 +        self.assertEqual( len( skins_tool.objectItems() ), 0 )
  34.468 +
  34.469 +        context = DummyImportContext( site )
  34.470 +        context._files[ 'skins.xml' ] = _NORMAL_EXPORT
  34.471 +
  34.472 +        from Products.CMFSetup.skins import importSkinsTool
  34.473 +        importSkinsTool( context )
  34.474 +
  34.475 +        self.assertEqual( skins_tool.default_skin, "basic" )
  34.476 +        self.assertEqual( skins_tool.request_varname, "skin_var" )
  34.477 +        self.failUnless( skins_tool.allow_any )
  34.478 +        self.failUnless( skins_tool.cookie_persistence )
  34.479 +
  34.480 +        self.failUnless( site._skin_setup_called )
  34.481 +        self.assertEqual( len( skins_tool.getSkinPaths() ), 2 )
  34.482 +        self.assertEqual( len( skins_tool.objectItems() ), 3 )
  34.483 +
  34.484 +    def test_normal_encode_as_ascii( self ):
  34.485 +
  34.486 +        site = self._initSite()
  34.487 +        self._registerDirectoryView( os.path.join( _TESTS_PATH, 'one' ) )
  34.488 +        self._registerDirectoryView( os.path.join( _TESTS_PATH, 'two' ) )
  34.489 +        self._registerDirectoryView( os.path.join( _TESTS_PATH, 'three' ) )
  34.490 +        skins_tool = site.portal_skins
  34.491 +
  34.492 +        self.failIf( site._skin_setup_called )
  34.493 +        self.assertEqual( len( skins_tool.getSkinPaths() ), 0 )
  34.494 +        self.assertEqual( len( skins_tool.objectItems() ), 0 )
  34.495 +
  34.496 +        context = DummyImportContext( site, encoding='ascii' )
  34.497 +        context._files[ 'skins.xml' ] = _NORMAL_EXPORT
  34.498 +
  34.499 +        from Products.CMFSetup.skins import importSkinsTool
  34.500 +        importSkinsTool( context )
  34.501 +
  34.502 +        self.assertEqual( skins_tool.default_skin, "basic" )
  34.503 +        self.assertEqual( skins_tool.request_varname, "skin_var" )
  34.504 +        self.failUnless( skins_tool.allow_any )
  34.505 +        self.failUnless( skins_tool.cookie_persistence )
  34.506 +
  34.507 +        self.failUnless( site._skin_setup_called )
  34.508 +        self.assertEqual( len( skins_tool.getSkinPaths() ), 2 )
  34.509 +        self.assertEqual( len( skins_tool.objectItems() ), 3 )
  34.510 +
  34.511 +    def test_fragment_skip_purge(self):
  34.512 +
  34.513 +        _IDS = ( 'one', 'two' )
  34.514 +        _FSDVS = [ ( id, DummyFSDV( id ) ) for id in _IDS ]
  34.515 +        _PATHS = { 'basic' : 'one', 'fancy' : 'two,one' }
  34.516 +
  34.517 +        site = self._initSite( selections=_PATHS, fsdvs=_FSDVS )
  34.518 +        self._registerDirectoryView( os.path.join( _TESTS_PATH, 'three' ) )
  34.519 +        skins_tool = site.portal_skins
  34.520 +
  34.521 +        self.failIf( site._skin_setup_called )
  34.522 +        skin_paths = skins_tool.getSkinPaths()
  34.523 +        self.assertEqual( len( skin_paths ), 2 )
  34.524 +        self.assertEqual( skin_paths[ 0 ], ( 'basic', 'one' ) )
  34.525 +        self.assertEqual( skin_paths[ 1 ], ( 'fancy', 'two,one' ) )
  34.526 +        self.assertEqual( len( skins_tool.objectItems() ), 2 )
  34.527 +
  34.528 +        context = DummyImportContext( site, False )
  34.529 +        context._files[ 'skins.xml' ] = _FRAGMENT_IMPORT
  34.530 +
  34.531 +        from Products.CMFSetup.skins import importSkinsTool
  34.532 +        importSkinsTool( context )
  34.533 +
  34.534 +        self.assertEqual( skins_tool.default_skin, "default_skin" )
  34.535 +        self.assertEqual( skins_tool.request_varname, "request_varname" )
  34.536 +        self.failIf( skins_tool.allow_any )
  34.537 +        self.failIf( skins_tool.cookie_persistence )
  34.538 +
  34.539 +        self.failUnless( site._skin_setup_called )
  34.540 +        skin_paths = skins_tool.getSkinPaths()
  34.541 +        self.assertEqual( len( skin_paths ), 2 )
  34.542 +        self.assertEqual( skin_paths[ 0 ], ( 'basic', 'one,three' ) )
  34.543 +        self.assertEqual( skin_paths[ 1 ], ( 'fancy', 'three,two,one' ) )
  34.544 +        self.assertEqual( len( skins_tool.objectItems() ), 3 )
  34.545 +
  34.546 +        self._registerDirectoryView( os.path.join( _TESTS_PATH, 'four' ) )
  34.547 +        context._files[ 'skins.xml' ] = _FRAGMENT2_IMPORT
  34.548 +        importSkinsTool( context )
  34.549 +
  34.550 +        self.assertEqual( skins_tool.default_skin, "default_skin" )
  34.551 +        self.assertEqual( skins_tool.request_varname, "request_varname" )
  34.552 +        self.failIf( skins_tool.allow_any )
  34.553 +        self.failIf( skins_tool.cookie_persistence )
  34.554 +
  34.555 +        self.failUnless( site._skin_setup_called )
  34.556 +        skin_paths = skins_tool.getSkinPaths()
  34.557 +        self.assertEqual( len( skin_paths ), 2 )
  34.558 +        self.assertEqual( skin_paths[ 0 ], ( 'basic', 'one,three,four' ) )
  34.559 +        self.assertEqual( skin_paths[ 1 ], ( 'fancy', 'three,four,two,one' ) )
  34.560 +        self.assertEqual( len( skins_tool.objectItems() ), 4 )
  34.561 +
  34.562 +    def test_fragment3_skip_purge(self):
  34.563 +
  34.564 +        _IDS = ( 'one', 'two' )
  34.565 +        _FSDVS = [ ( id, DummyFSDV( id ) ) for id in _IDS ]
  34.566 +        _PATHS = { 'basic' : 'one', 'fancy' : 'two,one' }
  34.567 +
  34.568 +        site = self._initSite( selections=_PATHS, fsdvs=_FSDVS )
  34.569 +        self._registerDirectoryView( os.path.join( _TESTS_PATH, 'three' ) )
  34.570 +        self._registerDirectoryView( os.path.join( _TESTS_PATH, 'four' ) )
  34.571 +        skins_tool = site.portal_skins
  34.572 +
  34.573 +        self.failIf( site._skin_setup_called )
  34.574 +        skin_paths = skins_tool.getSkinPaths()
  34.575 +        self.assertEqual( len( skin_paths ), 2 )
  34.576 +        self.assertEqual( skin_paths[ 0 ], ( 'basic', 'one' ) )
  34.577 +        self.assertEqual( skin_paths[ 1 ], ( 'fancy', 'two,one' ) )
  34.578 +        self.assertEqual( len( skins_tool.objectItems() ), 2 )
  34.579 +
  34.580 +        context = DummyImportContext( site, False )
  34.581 +        context._files[ 'skins.xml' ] = _FRAGMENT3_IMPORT
  34.582 +
  34.583 +        from Products.CMFSetup.skins import importSkinsTool
  34.584 +        importSkinsTool( context )
  34.585 +
  34.586 +        self.assertEqual( skins_tool.default_skin, "default_skin" )
  34.587 +        self.assertEqual( skins_tool.request_varname, "request_varname" )
  34.588 +        self.failIf( skins_tool.allow_any )
  34.589 +        self.failIf( skins_tool.cookie_persistence )
  34.590 +
  34.591 +        self.failUnless( site._skin_setup_called )
  34.592 +        skin_paths = skins_tool.getSkinPaths()
  34.593 +        self.assertEqual( len( skin_paths ), 2 )
  34.594 +        self.assertEqual( skin_paths[ 0 ], ( 'basic', 'three,one,four' ) )
  34.595 +        self.assertEqual( skin_paths[ 1 ],
  34.596 +                          ( 'fancy', 'three,two,one,four' ) )
  34.597 +        self.assertEqual( len( skins_tool.objectItems() ), 4 )
  34.598 +
  34.599 +    def test_fragment4_removal(self):
  34.600 +
  34.601 +        _IDS = ( 'one', 'two' )
  34.602 +        _FSDVS = [ ( id, DummyFSDV( id ) ) for id in _IDS ]
  34.603 +        _PATHS = { 'basic' : 'one', 'fancy' : 'two,one' }
  34.604 +
  34.605 +        site = self._initSite( selections=_PATHS, fsdvs=_FSDVS )
  34.606 +        self._registerDirectoryView( os.path.join( _TESTS_PATH, 'three' ) )
  34.607 +        self._registerDirectoryView( os.path.join( _TESTS_PATH, 'four' ) )
  34.608 +        skins_tool = site.portal_skins
  34.609 +
  34.610 +        skin_paths = skins_tool.getSkinPaths()
  34.611 +        self.assertEqual( len( skin_paths ), 2 )
  34.612 +        self.assertEqual( skin_paths[ 0 ], ( 'basic', 'one' ) )
  34.613 +        self.assertEqual( skin_paths[ 1 ], ( 'fancy', 'two,one' ) )
  34.614 +        self.assertEqual( len( skins_tool.objectItems() ), 2 )
  34.615 +
  34.616 +        context = DummyImportContext( site, False )
  34.617 +        context._files[ 'skins.xml' ] = _FRAGMENT3_IMPORT
  34.618 +
  34.619 +        from Products.CMFSetup.skins import importSkinsTool
  34.620 +        importSkinsTool( context )
  34.621 +
  34.622 +        self.failUnless( site._skin_setup_called )
  34.623 +        skin_paths = skins_tool.getSkinPaths()
  34.624 +        self.assertEqual( len( skin_paths ), 2 )
  34.625 +        self.assertEqual( skin_paths[ 0 ], ( 'basic', 'three,one,four' ) )
  34.626 +        self.assertEqual( skin_paths[ 1 ],
  34.627 +                          ( 'fancy', 'three,two,one,four' ) )
  34.628 +        self.assertEqual( len( skins_tool.objectItems() ), 4 )
  34.629 +
  34.630 +        context = DummyImportContext( site, False )
  34.631 +        context._files[ 'skins.xml' ] = _FRAGMENT4_IMPORT
  34.632 +
  34.633 +        importSkinsTool( context )
  34.634 +
  34.635 +        self.failUnless( site._skin_setup_called )
  34.636 +        skin_paths = skins_tool.getSkinPaths()
  34.637 +        self.assertEqual( len( skin_paths ), 2 )
  34.638 +        self.assertEqual( skin_paths[ 0 ], ( 'basic', 'one,four' ) )
  34.639 +        self.assertEqual( skin_paths[ 1 ],
  34.640 +                          ( 'fancy', 'two,one,four' ) )
  34.641 +        self.assertEqual( len( skins_tool.objectItems() ), 4 )
  34.642 +
  34.643 +
  34.644 +def test_suite():
  34.645 +    return unittest.TestSuite((
  34.646 +        unittest.makeSuite( SkinsToolConfiguratorTests ),
  34.647 +        unittest.makeSuite( Test_exportSkinsTool ),
  34.648 +        unittest.makeSuite( Test_importSkinsTool ),
  34.649 +        ))
  34.650 +
  34.651 +if __name__ == '__main__':
  34.652 +    unittest.main(defaultTest='test_suite')
    35.1 new file mode 100644
    35.2 --- /dev/null
    35.3 +++ b/tests/test_tool.py
    35.4 @@ -0,0 +1,895 @@
    35.5 +##############################################################################
    35.6 +#
    35.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    35.8 +#
    35.9 +# This software is subject to the provisions of the Zope Public License,
   35.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   35.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   35.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   35.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   35.14 +# FOR A PARTICULAR PURPOSE.
   35.15 +#
   35.16 +##############################################################################
   35.17 +""" Unit tests for CMFSetup tool.
   35.18 +
   35.19 +$Id: test_tool.py 37135 2005-07-08 13:24:33Z tseaver $
   35.20 +"""
   35.21 +
   35.22 +import unittest
   35.23 +import Testing
   35.24 +try:
   35.25 +    import Zope2
   35.26 +except ImportError: # BBB: for Zope 2.7
   35.27 +    import Zope as Zope2
   35.28 +Zope2.startup()
   35.29 +
   35.30 +from StringIO import StringIO
   35.31 +
   35.32 +from Acquisition import aq_base
   35.33 +from OFS.Folder import Folder
   35.34 +
   35.35 +from Products.CMFSetup import profile_registry
   35.36 +
   35.37 +from common import DOMComparator
   35.38 +from common import DummyExportContext
   35.39 +from common import DummyImportContext
   35.40 +from common import FilesystemTestBase
   35.41 +from common import SecurityRequestTest
   35.42 +from common import TarballTester
   35.43 +from conformance import ConformsToISetupTool
   35.44 +
   35.45 +
   35.46 +class SetupToolTests( FilesystemTestBase
   35.47 +                    , TarballTester
   35.48 +                    , ConformsToISetupTool
   35.49 +                    ):
   35.50 +
   35.51 +    _PROFILE_PATH = '/tmp/STT_test'
   35.52 +
   35.53 +    def setUp( self ):
   35.54 +
   35.55 +        FilesystemTestBase.setUp( self )
   35.56 +        self._profile_registry_info = profile_registry._profile_info
   35.57 +        self._profile_registry_ids = profile_registry._profile_ids
   35.58 +        profile_registry.clear()
   35.59 +
   35.60 +    def tearDown( self ):
   35.61 +
   35.62 +        profile_registry._profile_info = self._profile_registry_info
   35.63 +        profile_registry._profile_ids = self._profile_registry_ids
   35.64 +        FilesystemTestBase.tearDown( self )
   35.65 +
   35.66 +    def _getTargetClass( self ):
   35.67 +
   35.68 +        from Products.CMFSetup.tool import SetupTool
   35.69 +        return SetupTool
   35.70 +
   35.71 +    def _makeOne( self, *args, **kw ):
   35.72 +
   35.73 +        return self._getTargetClass()( *args, **kw )
   35.74 +
   35.75 +    def _makeSite( self, title="Don't care" ):
   35.76 +
   35.77 +        site = Folder()
   35.78 +        site._setId( 'site' )
   35.79 +        site.title = title
   35.80 +
   35.81 +        self.root._setObject( 'site', site )
   35.82 +        return self.root._getOb( 'site' )
   35.83 +
   35.84 +    def test_empty( self ):
   35.85 +
   35.86 +        tool = self._makeOne()
   35.87 +
   35.88 +        self.assertEqual( tool.getImportContextID(), '' )
   35.89 +
   35.90 +        import_registry = tool.getImportStepRegistry()
   35.91 +        self.assertEqual( len( import_registry.listSteps() ), 0 )
   35.92 +
   35.93 +        export_registry = tool.getExportStepRegistry()
   35.94 +        export_steps = export_registry.listSteps()
   35.95 +        self.assertEqual( len( export_steps ), 1 )
   35.96 +        self.assertEqual( export_steps[ 0 ], 'step_registries' )
   35.97 +
   35.98 +        toolset_registry = tool.getToolsetRegistry()
   35.99 +        self.assertEqual( len( toolset_registry.listForbiddenTools() ), 0 )
  35.100 +        self.assertEqual( len( toolset_registry.listRequiredTools() ), 0 )
  35.101 +
  35.102 +    def test_getImportContextID( self ):
  35.103 +
  35.104 +        from Products.CMFSetup.tool import IMPORT_STEPS_XML
  35.105 +        from Products.CMFSetup.tool import EXPORT_STEPS_XML
  35.106 +        from Products.CMFSetup.tool import TOOLSET_XML
  35.107 +        from test_registry import _EMPTY_IMPORT_XML
  35.108 +        from test_registry import _EMPTY_EXPORT_XML
  35.109 +        from test_registry import _EMPTY_TOOLSET_XML
  35.110 +        from common import _makeTestFile
  35.111 +
  35.112 +        tool = self._makeOne()
  35.113 +
  35.114 +        _makeTestFile( IMPORT_STEPS_XML
  35.115 +                     , self._PROFILE_PATH
  35.116 +                     , _EMPTY_IMPORT_XML
  35.117 +                     )
  35.118 +
  35.119 +        _makeTestFile( EXPORT_STEPS_XML
  35.120 +                     , self._PROFILE_PATH
  35.121 +                     , _EMPTY_EXPORT_XML
  35.122 +                     )
  35.123 +
  35.124 +        _makeTestFile( TOOLSET_XML
  35.125 +                     , self._PROFILE_PATH
  35.126 +                     , _EMPTY_TOOLSET_XML
  35.127 +                     )
  35.128 +
  35.129 +        profile_registry.registerProfile('foo', 'Foo', '', self._PROFILE_PATH)
  35.130 +        tool.setImportContext('profile-other:foo')
  35.131 +
  35.132 +        self.assertEqual( tool.getImportContextID(), 'profile-other:foo' )
  35.133 +
  35.134 +    def test_setImportContext_invalid( self ):
  35.135 +
  35.136 +        tool = self._makeOne()
  35.137 +
  35.138 +        self.assertRaises( KeyError
  35.139 +                         , tool.setImportContext
  35.140 +                         , 'profile-foo'
  35.141 +                         )
  35.142 +
  35.143 +    def test_setImportContext( self ):
  35.144 +
  35.145 +        from Products.CMFSetup.tool import IMPORT_STEPS_XML
  35.146 +        from Products.CMFSetup.tool import EXPORT_STEPS_XML
  35.147 +        from Products.CMFSetup.tool import TOOLSET_XML
  35.148 +        from test_registry import _SINGLE_IMPORT_XML
  35.149 +        from test_registry import _SINGLE_EXPORT_XML
  35.150 +        from test_registry import _NORMAL_TOOLSET_XML
  35.151 +        from test_registry import ONE_FUNC
  35.152 +        from common import _makeTestFile
  35.153 +
  35.154 +        tool = self._makeOne()
  35.155 +        tool.getExportStepRegistry().clear()
  35.156 +
  35.157 +        _makeTestFile( IMPORT_STEPS_XML
  35.158 +                     , self._PROFILE_PATH
  35.159 +                     , _SINGLE_IMPORT_XML
  35.160 +                     )
  35.161 +
  35.162 +        _makeTestFile( EXPORT_STEPS_XML
  35.163 +                     , self._PROFILE_PATH
  35.164 +                     , _SINGLE_EXPORT_XML
  35.165 +                     )
  35.166 +
  35.167 +        _makeTestFile( TOOLSET_XML
  35.168 +                     , self._PROFILE_PATH
  35.169 +                     , _NORMAL_TOOLSET_XML
  35.170 +                     )
  35.171 +
  35.172 +        profile_registry.registerProfile('foo', 'Foo', '', self._PROFILE_PATH)
  35.173 +        tool.setImportContext('profile-other:foo')
  35.174 +
  35.175 +        self.assertEqual( tool.getImportContextID(), 'profile-other:foo' )
  35.176 +
  35.177 +        import_registry = tool.getImportStepRegistry()
  35.178 +        self.assertEqual( len( import_registry.listSteps() ), 1 )
  35.179 +        self.failUnless( 'one' in import_registry.listSteps() )
  35.180 +        info = import_registry.getStepMetadata( 'one' )
  35.181 +        self.assertEqual( info[ 'id' ], 'one' )
  35.182 +        self.assertEqual( info[ 'title' ], 'One Step' )
  35.183 +        self.assertEqual( info[ 'version' ], '1' )
  35.184 +        self.failUnless( 'One small step' in info[ 'description' ] )
  35.185 +        self.assertEqual( info[ 'handler' ]
  35.186 +                        , 'Products.CMFSetup.tests.test_registry.ONE_FUNC' )
  35.187 +
  35.188 +        self.assertEqual( import_registry.getStep( 'one' ), ONE_FUNC )
  35.189 +
  35.190 +        export_registry = tool.getExportStepRegistry()
  35.191 +        self.assertEqual( len( export_registry.listSteps() ), 1 )
  35.192 +        self.failUnless( 'one' in import_registry.listSteps() )
  35.193 +        info = export_registry.getStepMetadata( 'one' )
  35.194 +        self.assertEqual( info[ 'id' ], 'one' )
  35.195 +        self.assertEqual( info[ 'title' ], 'One Step' )
  35.196 +        self.failUnless( 'One small step' in info[ 'description' ] )
  35.197 +        self.assertEqual( info[ 'handler' ]
  35.198 +                        , 'Products.CMFSetup.tests.test_registry.ONE_FUNC' )
  35.199 +
  35.200 +        self.assertEqual( export_registry.getStep( 'one' ), ONE_FUNC )
  35.201 +
  35.202 +        toolset = tool.getToolsetRegistry()
  35.203 +        self.assertEqual( len( toolset.listForbiddenTools() ), 1 )
  35.204 +        self.failUnless( 'doomed' in toolset.listForbiddenTools() )
  35.205 +        self.assertEqual( len( toolset.listRequiredTools() ), 2 )
  35.206 +        self.failUnless( 'mandatory' in toolset.listRequiredTools() )
  35.207 +        info = toolset.getRequiredToolInfo( 'mandatory' )
  35.208 +        self.assertEqual( info[ 'class' ], 'path.to.one' )
  35.209 +        self.failUnless( 'obligatory' in toolset.listRequiredTools() )
  35.210 +        info = toolset.getRequiredToolInfo( 'obligatory' )
  35.211 +        self.assertEqual( info[ 'class' ], 'path.to.another' )
  35.212 +
  35.213 +    def test_runImportStep_nonesuch( self ):
  35.214 +
  35.215 +        site = self._makeSite()
  35.216 +
  35.217 +        tool = self._makeOne().__of__( site )
  35.218 +
  35.219 +        self.assertRaises( ValueError, tool.runImportStep, 'nonesuch' )
  35.220 +
  35.221 +    def test_runImportStep_simple( self ):
  35.222 +
  35.223 +        TITLE = 'original title'
  35.224 +        site = self._makeSite( TITLE )
  35.225 +
  35.226 +        tool = self._makeOne().__of__( site )
  35.227 +
  35.228 +        registry = tool.getImportStepRegistry()
  35.229 +        registry.registerStep( 'simple', '1', _uppercaseSiteTitle )
  35.230 +
  35.231 +        result = tool.runImportStep( 'simple' )
  35.232 +
  35.233 +        self.assertEqual( len( result[ 'steps' ] ), 1 )
  35.234 +
  35.235 +        self.assertEqual( result[ 'steps' ][ 0 ], 'simple' )
  35.236 +        self.assertEqual( result[ 'messages' ][ 'simple' ]
  35.237 +                        , 'Uppercased title' )
  35.238 +
  35.239 +        self.assertEqual( site.title, TITLE.upper() )
  35.240 +
  35.241 +    def test_runImportStep_dependencies( self ):
  35.242 +
  35.243 +        TITLE = 'original title'
  35.244 +        site = self._makeSite( TITLE )
  35.245 +
  35.246 +        tool = self._makeOne().__of__( site )
  35.247 +
  35.248 +        registry = tool.getImportStepRegistry()
  35.249 +        registry.registerStep( 'dependable', '1', _underscoreSiteTitle )
  35.250 +        registry.registerStep( 'dependent', '1'
  35.251 +                             , _uppercaseSiteTitle, ( 'dependable', ) )
  35.252 +
  35.253 +        result = tool.runImportStep( 'dependent' )
  35.254 +
  35.255 +        self.assertEqual( len( result[ 'steps' ] ), 2 )
  35.256 +
  35.257 +        self.assertEqual( result[ 'steps' ][ 0 ], 'dependable' )
  35.258 +        self.assertEqual( result[ 'messages' ][ 'dependable' ]
  35.259 +                        , 'Underscored title' )
  35.260 +
  35.261 +        self.assertEqual( result[ 'steps' ][ 1 ], 'dependent' )
  35.262 +        self.assertEqual( result[ 'messages' ][ 'dependent' ]
  35.263 +                        , 'Uppercased title' )
  35.264 +        self.assertEqual( site.title, TITLE.replace( ' ', '_' ).upper() )
  35.265 +
  35.266 +    def test_runImportStep_skip_dependencies( self ):
  35.267 +
  35.268 +        TITLE = 'original title'
  35.269 +        site = self._makeSite( TITLE )
  35.270 +
  35.271 +        tool = self._makeOne().__of__( site )
  35.272 +
  35.273 +        registry = tool.getImportStepRegistry()
  35.274 +        registry.registerStep( 'dependable', '1', _underscoreSiteTitle )
  35.275 +        registry.registerStep( 'dependent', '1'
  35.276 +                             , _uppercaseSiteTitle, ( 'dependable', ) )
  35.277 +
  35.278 +        result = tool.runImportStep( 'dependent', run_dependencies=False )
  35.279 +
  35.280 +        self.assertEqual( len( result[ 'steps' ] ), 1 )
  35.281 +
  35.282 +        self.assertEqual( result[ 'steps' ][ 0 ], 'dependent' )
  35.283 +        self.assertEqual( result[ 'messages' ][ 'dependent' ]
  35.284 +                        , 'Uppercased title' )
  35.285 +
  35.286 +        self.assertEqual( site.title, TITLE.upper() )
  35.287 +
  35.288 +    def test_runImportStep_default_purge( self ):
  35.289 +
  35.290 +        site = self._makeSite()
  35.291 +
  35.292 +        tool = self._makeOne().__of__( site )
  35.293 +        registry = tool.getImportStepRegistry()
  35.294 +        registry.registerStep( 'purging', '1', _purgeIfRequired )
  35.295 +
  35.296 +        result = tool.runImportStep( 'purging' )
  35.297 +
  35.298 +        self.assertEqual( len( result[ 'steps' ] ), 1 )
  35.299 +        self.assertEqual( result[ 'steps' ][ 0 ], 'purging' )
  35.300 +        self.assertEqual( result[ 'messages' ][ 'purging' ], 'Purged' )
  35.301 +        self.failUnless( site.purged )
  35.302 +
  35.303 +    def test_runImportStep_explicit_purge( self ):
  35.304 +
  35.305 +        site = self._makeSite()
  35.306 +
  35.307 +        tool = self._makeOne().__of__( site )
  35.308 +        registry = tool.getImportStepRegistry()
  35.309 +        registry.registerStep( 'purging', '1', _purgeIfRequired )
  35.310 +
  35.311 +        result = tool.runImportStep( 'purging', purge_old=True )
  35.312 +
  35.313 +        self.assertEqual( len( result[ 'steps' ] ), 1 )
  35.314 +        self.assertEqual( result[ 'steps' ][ 0 ], 'purging' )
  35.315 +        self.assertEqual( result[ 'messages' ][ 'purging' ], 'Purged' )
  35.316 +        self.failUnless( site.purged )
  35.317 +
  35.318 +    def test_runImportStep_skip_purge( self ):
  35.319 +
  35.320 +        site = self._makeSite()
  35.321 +
  35.322 +        tool = self._makeOne().__of__( site )
  35.323 +        registry = tool.getImportStepRegistry()
  35.324 +        registry.registerStep( 'purging', '1', _purgeIfRequired )
  35.325 +
  35.326 +        result = tool.runImportStep( 'purging', purge_old=False )
  35.327 +
  35.328 +        self.assertEqual( len( result[ 'steps' ] ), 1 )
  35.329 +        self.assertEqual( result[ 'steps' ][ 0 ], 'purging' )
  35.330 +        self.assertEqual( result[ 'messages' ][ 'purging' ], 'Unpurged' )
  35.331 +        self.failIf( site.purged )
  35.332 +
  35.333 +    def test_runImportStep_consistent_context( self ):
  35.334 +
  35.335 +        site = self._makeSite()
  35.336 +
  35.337 +        tool = self._makeOne().__of__( site )
  35.338 +
  35.339 +        registry = tool.getImportStepRegistry()
  35.340 +        registry.registerStep( 'purging', '1', _purgeIfRequired )
  35.341 +        registry.registerStep( 'dependent', '1'
  35.342 +                             , _uppercaseSiteTitle, ( 'purging', ) )
  35.343 +
  35.344 +        result = tool.runImportStep( 'dependent', purge_old=False )
  35.345 +        self.failIf( site.purged )
  35.346 +
  35.347 +    def test_runAllImportSteps_empty( self ):
  35.348 +
  35.349 +        site = self._makeSite()
  35.350 +        tool = self._makeOne().__of__( site )
  35.351 +
  35.352 +        result = tool.runAllImportSteps()
  35.353 +
  35.354 +        self.assertEqual( len( result[ 'steps' ] ), 0 )
  35.355 +
  35.356 +    def test_runAllImportSteps_sorted_default_purge( self ):
  35.357 +
  35.358 +        TITLE = 'original title'
  35.359 +        site = self._makeSite( TITLE )
  35.360 +        tool = self._makeOne().__of__( site )
  35.361 +
  35.362 +        registry = tool.getImportStepRegistry()
  35.363 +        registry.registerStep( 'dependable', '1'
  35.364 +                             , _underscoreSiteTitle, ( 'purging', ) )
  35.365 +        registry.registerStep( 'dependent', '1'
  35.366 +                             , _uppercaseSiteTitle, ( 'dependable', ) )
  35.367 +        registry.registerStep( 'purging', '1'
  35.368 +                             , _purgeIfRequired )
  35.369 +
  35.370 +        result = tool.runAllImportSteps()
  35.371 +
  35.372 +        self.assertEqual( len( result[ 'steps' ] ), 3 )
  35.373 +
  35.374 +        self.assertEqual( result[ 'steps' ][ 0 ], 'purging' )
  35.375 +        self.assertEqual( result[ 'messages' ][ 'purging' ]
  35.376 +                        , 'Purged' )
  35.377 +
  35.378 +        self.assertEqual( result[ 'steps' ][ 1 ], 'dependable' )
  35.379 +        self.assertEqual( result[ 'messages' ][ 'dependable' ]
  35.380 +                        , 'Underscored title' )
  35.381 +
  35.382 +        self.assertEqual( result[ 'steps' ][ 2 ], 'dependent' )
  35.383 +        self.assertEqual( result[ 'messages' ][ 'dependent' ]
  35.384 +                        , 'Uppercased title' )
  35.385 +
  35.386 +        self.assertEqual( site.title, TITLE.replace( ' ', '_' ).upper() )
  35.387 +        self.failUnless( site.purged )
  35.388 +
  35.389 +    def test_runAllImportSteps_sorted_explicit_purge( self ):
  35.390 +
  35.391 +        site = self._makeSite()
  35.392 +        tool = self._makeOne().__of__( site )
  35.393 +
  35.394 +        registry = tool.getImportStepRegistry()
  35.395 +        registry.registerStep( 'dependable', '1'
  35.396 +                             , _underscoreSiteTitle, ( 'purging', ) )
  35.397 +        registry.registerStep( 'dependent', '1'
  35.398 +                             , _uppercaseSiteTitle, ( 'dependable', ) )
  35.399 +        registry.registerStep( 'purging', '1'
  35.400 +                             , _purgeIfRequired )
  35.401 +
  35.402 +        result = tool.runAllImportSteps( purge_old=True )
  35.403 +
  35.404 +        self.assertEqual( len( result[ 'steps' ] ), 3 )
  35.405 +
  35.406 +        self.assertEqual( result[ 'steps' ][ 0 ], 'purging' )
  35.407 +        self.assertEqual( result[ 'messages' ][ 'purging' ]
  35.408 +                        , 'Purged' )
  35.409 +
  35.410 +        self.assertEqual( result[ 'steps' ][ 1 ], 'dependable' )
  35.411 +        self.assertEqual( result[ 'steps' ][ 2 ], 'dependent' )
  35.412 +        self.failUnless( site.purged )
  35.413 +
  35.414 +    def test_runAllImportSteps_sorted_skip_purge( self ):
  35.415 +
  35.416 +        site = self._makeSite()
  35.417 +        tool = self._makeOne().__of__( site )
  35.418 +
  35.419 +        registry = tool.getImportStepRegistry()
  35.420 +        registry.registerStep( 'dependable', '1'
  35.421 +                             , _underscoreSiteTitle, ( 'purging', ) )
  35.422 +        registry.registerStep( 'dependent', '1'
  35.423 +                             , _uppercaseSiteTitle, ( 'dependable', ) )
  35.424 +        registry.registerStep( 'purging', '1'
  35.425 +                             , _purgeIfRequired )
  35.426 +
  35.427 +        result = tool.runAllImportSteps( purge_old=False )
  35.428 +
  35.429 +        self.assertEqual( len( result[ 'steps' ] ), 3 )
  35.430 +
  35.431 +        self.assertEqual( result[ 'steps' ][ 0 ], 'purging' )
  35.432 +        self.assertEqual( result[ 'messages' ][ 'purging' ]
  35.433 +                        , 'Unpurged' )
  35.434 +
  35.435 +        self.assertEqual( result[ 'steps' ][ 1 ], 'dependable' )
  35.436 +        self.assertEqual( result[ 'steps' ][ 2 ], 'dependent' )
  35.437 +        self.failIf( site.purged )
  35.438 +
  35.439 +    def test_runExportStep_nonesuch( self ):
  35.440 +
  35.441 +        site = self._makeSite()
  35.442 +        tool = self._makeOne().__of__( site )
  35.443 +
  35.444 +        self.assertRaises( ValueError, tool.runExportStep, 'nonesuch' )
  35.445 +
  35.446 +    def test_runExportStep_step_registry( self ):
  35.447 +
  35.448 +        from test_registry import _EMPTY_IMPORT_XML
  35.449 +
  35.450 +        site = self._makeSite()
  35.451 +        site.portal_setup = self._makeOne()
  35.452 +        tool = site.portal_setup
  35.453 +
  35.454 +        result = tool.runExportStep( 'step_registries' )
  35.455 +
  35.456 +        self.assertEqual( len( result[ 'steps' ] ), 1 )
  35.457 +        self.assertEqual( result[ 'steps' ][ 0 ], 'step_registries' )
  35.458 +        self.assertEqual( result[ 'messages' ][ 'step_registries' ]
  35.459 +                        , 'Step registries exported'
  35.460 +                        )
  35.461 +        fileish = StringIO( result[ 'tarball' ] )
  35.462 +
  35.463 +        self._verifyTarballContents( fileish, [ 'import_steps.xml'
  35.464 +                                              , 'export_steps.xml'
  35.465 +                                              ] )
  35.466 +        self._verifyTarballEntryXML( fileish, 'import_steps.xml'
  35.467 +                                   , _EMPTY_IMPORT_XML )
  35.468 +        self._verifyTarballEntryXML( fileish, 'export_steps.xml'
  35.469 +                                   , _DEFAULT_STEP_REGISTRIES_EXPORT_XML )
  35.470 +
  35.471 +    def test_runAllExportSteps_default( self ):
  35.472 +
  35.473 +        from test_registry import _EMPTY_IMPORT_XML
  35.474 +
  35.475 +        site = self._makeSite()
  35.476 +        site.portal_setup = self._makeOne()
  35.477 +        tool = site.portal_setup
  35.478 +
  35.479 +        result = tool.runAllExportSteps()
  35.480 +
  35.481 +        self.assertEqual( len( result[ 'steps' ] ), 1 )
  35.482 +        self.assertEqual( result[ 'steps' ][ 0 ], 'step_registries' )
  35.483 +        self.assertEqual( result[ 'messages' ][ 'step_registries' ]
  35.484 +                        , 'Step registries exported'
  35.485 +                        )
  35.486 +        fileish = StringIO( result[ 'tarball' ] )
  35.487 +
  35.488 +        self._verifyTarballContents( fileish, [ 'import_steps.xml'
  35.489 +                                              , 'export_steps.xml'
  35.490 +                                              ] )
  35.491 +        self._verifyTarballEntryXML( fileish, 'import_steps.xml'
  35.492 +                                   , _EMPTY_IMPORT_XML )
  35.493 +        self._verifyTarballEntryXML( fileish, 'export_steps.xml'
  35.494 +                                   , _DEFAULT_STEP_REGISTRIES_EXPORT_XML )
  35.495 +
  35.496 +    def test_runAllExportSteps_extras( self ):
  35.497 +
  35.498 +        from test_registry import _EMPTY_IMPORT_XML
  35.499 +
  35.500 +        site = self._makeSite()
  35.501 +        site.portal_setup = self._makeOne()
  35.502 +        tool = site.portal_setup
  35.503 +
  35.504 +        import_reg = tool.getImportStepRegistry()
  35.505 +        import_reg.registerStep( 'dependable', '1'
  35.506 +                               , _underscoreSiteTitle, ( 'purging', ) )
  35.507 +        import_reg.registerStep( 'dependent', '1'
  35.508 +                               , _uppercaseSiteTitle, ( 'dependable', ) )
  35.509 +        import_reg.registerStep( 'purging', '1'
  35.510 +                               , _purgeIfRequired )
  35.511 +
  35.512 +        export_reg = tool.getExportStepRegistry()
  35.513 +        export_reg.registerStep( 'properties'
  35.514 +                               , _exportPropertiesINI )
  35.515 +
  35.516 +        result = tool.runAllExportSteps()
  35.517 +
  35.518 +        self.assertEqual( len( result[ 'steps' ] ), 2 )
  35.519 +
  35.520 +        self.failUnless( 'properties' in result[ 'steps' ] )
  35.521 +        self.assertEqual( result[ 'messages' ][ 'properties' ]
  35.522 +                        , 'Exported properties'
  35.523 +                        )
  35.524 +
  35.525 +        self.failUnless( 'step_registries' in result[ 'steps' ] )
  35.526 +        self.assertEqual( result[ 'messages' ][ 'step_registries' ]
  35.527 +                        , 'Step registries exported'
  35.528 +                        )
  35.529 +
  35.530 +        fileish = StringIO( result[ 'tarball' ] )
  35.531 +
  35.532 +        self._verifyTarballContents( fileish, [ 'import_steps.xml'
  35.533 +                                              , 'export_steps.xml'
  35.534 +                                              , 'properties.ini'
  35.535 +                                              ] )
  35.536 +        self._verifyTarballEntryXML( fileish, 'import_steps.xml'
  35.537 +                                   , _EXTRAS_STEP_REGISTRIES_IMPORT_XML )
  35.538 +        self._verifyTarballEntryXML( fileish, 'export_steps.xml'
  35.539 +                                   , _EXTRAS_STEP_REGISTRIES_EXPORT_XML )
  35.540 +        self._verifyTarballEntry( fileish, 'properties.ini'
  35.541 +                                , _PROPERTIES_INI % site.title  )
  35.542 +
  35.543 +    def test_createSnapshot_default( self ):
  35.544 +
  35.545 +        from test_registry import _EMPTY_IMPORT_XML
  35.546 +
  35.547 +        _EXPECTED = [ ( 'import_steps.xml', _EMPTY_IMPORT_XML )
  35.548 +                    , ( 'export_steps.xml'
  35.549 +                      , _DEFAULT_STEP_REGISTRIES_EXPORT_XML
  35.550 +                      )
  35.551 +                    ]
  35.552 +
  35.553 +        site = self._makeSite()
  35.554 +        site.portal_setup = self._makeOne()
  35.555 +        tool = site.portal_setup
  35.556 +
  35.557 +        self.assertEqual( len( tool.listSnapshotInfo() ), 0 )
  35.558 +
  35.559 +        result = tool.createSnapshot( 'default' )
  35.560 +
  35.561 +        self.assertEqual( len( result[ 'steps' ] ), 1 )
  35.562 +        self.assertEqual( result[ 'steps' ][ 0 ], 'step_registries' )
  35.563 +        self.assertEqual( result[ 'messages' ][ 'step_registries' ]
  35.564 +                        , 'Step registries exported'
  35.565 +                        )
  35.566 +
  35.567 +        snapshot = result[ 'snapshot' ]
  35.568 +
  35.569 +        self.assertEqual( len( snapshot.objectIds() ), len( _EXPECTED ) )
  35.570 +
  35.571 +        for id in [ x[0] for x in _EXPECTED ]:
  35.572 +            self.failUnless( id in snapshot.objectIds() )
  35.573 +
  35.574 +        def normalize_xml(xml):
  35.575 +            # using this might mask a real problem on windows, but so far the
  35.576 +            # different newlines just caused problems in this test
  35.577 +            lines = [ line for line in xml.splitlines() if line ]
  35.578 +            return '\n'.join(lines) + '\n'
  35.579 +
  35.580 +        fileobj = snapshot._getOb( 'import_steps.xml' )
  35.581 +        self.assertEqual( normalize_xml( fileobj.read() ),
  35.582 +                          _EMPTY_IMPORT_XML )
  35.583 +
  35.584 +        fileobj = snapshot._getOb( 'export_steps.xml' )
  35.585 +
  35.586 +        self.assertEqual( normalize_xml( fileobj.read() ),
  35.587 +                          _DEFAULT_STEP_REGISTRIES_EXPORT_XML )
  35.588 +
  35.589 +        self.assertEqual( len( tool.listSnapshotInfo() ), 1 )
  35.590 +
  35.591 +        info = tool.listSnapshotInfo()[ 0 ]
  35.592 +
  35.593 +        self.assertEqual( info[ 'id' ], 'default' )
  35.594 +        self.assertEqual( info[ 'title' ], 'default' )
  35.595 +
  35.596 +
  35.597 +_DEFAULT_STEP_REGISTRIES_EXPORT_XML = """\
  35.598 +<?xml version="1.0"?>
  35.599 +<export-steps>
  35.600 + <export-step id="step_registries"
  35.601 +              handler="Products.CMFSetup.tool.exportStepRegistries"
  35.602 +              title="Export import / export steps.">
  35.603 +  
  35.604 + </export-step>
  35.605 +</export-steps>
  35.606 +"""
  35.607 +
  35.608 +_EXTRAS_STEP_REGISTRIES_EXPORT_XML = """\
  35.609 +<?xml version="1.0"?>
  35.610 +<export-steps>
  35.611 + <export-step id="properties"
  35.612 +              handler="Products.CMFSetup.tests.test_tool._exportPropertiesINI"
  35.613 +              title="properties">
  35.614 +
  35.615 + </export-step>
  35.616 + <export-step id="step_registries"
  35.617 +              handler="Products.CMFSetup.tool.exportStepRegistries"
  35.618 +              title="Export import / export steps.">
  35.619 +
  35.620 + </export-step>
  35.621 +</export-steps>
  35.622 +"""
  35.623 +
  35.624 +_EXTRAS_STEP_REGISTRIES_IMPORT_XML = """\
  35.625 +<?xml version="1.0"?>
  35.626 +<import-steps>
  35.627 + <import-step id="dependable"
  35.628 +              version="1"
  35.629 +              handler="Products.CMFSetup.tests.test_tool._underscoreSiteTitle"
  35.630 +              title="dependable">
  35.631 +  <dependency step="purging" />
  35.632 +
  35.633 + </import-step>
  35.634 + <import-step id="dependent"
  35.635 +              version="1"
  35.636 +              handler="Products.CMFSetup.tests.test_tool._uppercaseSiteTitle"
  35.637 +              title="dependent">
  35.638 +  <dependency step="dependable" />
  35.639 +
  35.640 + </import-step>
  35.641 + <import-step id="purging"
  35.642 +              version="1"
  35.643 +              handler="Products.CMFSetup.tests.test_tool._purgeIfRequired"
  35.644 +              title="purging">
  35.645 +
  35.646 + </import-step>
  35.647 +</import-steps>
  35.648 +"""
  35.649 +
  35.650 +_PROPERTIES_INI = """\
  35.651 +[Default]
  35.652 +Title=%s
  35.653 +"""
  35.654 +
  35.655 +def _underscoreSiteTitle( context ):
  35.656 +
  35.657 +    site = context.getSite()
  35.658 +    site.title = site.title.replace( ' ', '_' )
  35.659 +    return 'Underscored title'
  35.660 +
  35.661 +def _uppercaseSiteTitle( context ):
  35.662 +
  35.663 +    site = context.getSite()
  35.664 +    site.title = site.title.upper()
  35.665 +    return 'Uppercased title'
  35.666 +
  35.667 +def _purgeIfRequired( context ):
  35.668 +
  35.669 +    site = context.getSite()
  35.670 +    purged = site.purged = context.shouldPurge()
  35.671 +    return purged and 'Purged' or 'Unpurged'
  35.672 +
  35.673 +def _exportPropertiesINI( context ):
  35.674 +
  35.675 +    site = context.getSite()
  35.676 +    text = _PROPERTIES_INI % site.title
  35.677 +
  35.678 +    context.writeDataFile( 'properties.ini', text, 'text/plain' )
  35.679 +
  35.680 +    return 'Exported properties'
  35.681 +
  35.682 +class _ToolsetSetup( SecurityRequestTest ):
  35.683 +
  35.684 +    def _initSite( self ):
  35.685 +
  35.686 +        from Products.CMFSetup.tool import SetupTool
  35.687 +        site = Folder()
  35.688 +        site._setId( 'site' )
  35.689 +        self.root._setObject( 'site', site )
  35.690 +        site = self.root._getOb( 'site' )
  35.691 +        site._setObject( 'portal_setup', SetupTool() )
  35.692 +        return site
  35.693 +
  35.694 +class Test_exportToolset( _ToolsetSetup
  35.695 +                        , DOMComparator
  35.696 +                        ):
  35.697 +
  35.698 +    def test_empty( self ):
  35.699 +
  35.700 +        from Products.CMFSetup.tool import TOOLSET_XML
  35.701 +        from Products.CMFSetup.tool import exportToolset
  35.702 +
  35.703 +        site = self._initSite()
  35.704 +        context = DummyExportContext( site )
  35.705 +
  35.706 +        exportToolset( context )
  35.707 +
  35.708 +        self.assertEqual( len( context._wrote ), 1 )
  35.709 +        filename, text, content_type = context._wrote[ 0 ]
  35.710 +        self.assertEqual( filename, TOOLSET_XML )
  35.711 +        self._compareDOM( text, _EMPTY_TOOLSET_XML )
  35.712 +        self.assertEqual( content_type, 'text/xml' )
  35.713 +
  35.714 +    def test_normal( self ):
  35.715 +
  35.716 +        from Products.CMFSetup.tool import TOOLSET_XML
  35.717 +        from Products.CMFSetup.tool import exportToolset
  35.718 +
  35.719 +        site = self._initSite()
  35.720 +        toolset = site.portal_setup.getToolsetRegistry()
  35.721 +        toolset.addForbiddenTool( 'doomed' )
  35.722 +        toolset.addRequiredTool( 'mandatory', 'path.to.one' )
  35.723 +        toolset.addRequiredTool( 'obligatory', 'path.to.another' )
  35.724 +
  35.725 +        context = DummyExportContext( site )
  35.726 +
  35.727 +        exportToolset( context )
  35.728 +
  35.729 +        self.assertEqual( len( context._wrote ), 1 )
  35.730 +        filename, text, content_type = context._wrote[ 0 ]
  35.731 +        self.assertEqual( filename, TOOLSET_XML )
  35.732 +        self._compareDOM( text, _NORMAL_TOOLSET_XML )
  35.733 +        self.assertEqual( content_type, 'text/xml' )
  35.734 +
  35.735 +class Test_importToolset( _ToolsetSetup ):
  35.736 +
  35.737 +    def test_forbidden_tools( self ):
  35.738 +
  35.739 +        from Products.CMFSetup.tool import TOOLSET_XML
  35.740 +        from Products.CMFSetup.tool import importToolset
  35.741 +        TOOL_IDS = ( 'doomed', 'blasted', 'saved' )
  35.742 +
  35.743 +        site = self._initSite()
  35.744 +
  35.745 +        for tool_id in TOOL_IDS:
  35.746 +            pseudo = Folder()
  35.747 +            pseudo._setId( tool_id )
  35.748 +            site._setObject( tool_id, pseudo )
  35.749 +
  35.750 +        self.assertEqual( len( site.objectIds() ), len( TOOL_IDS ) + 1 )
  35.751 +
  35.752 +        for tool_id in TOOL_IDS:
  35.753 +            self.failUnless( tool_id in site.objectIds() )
  35.754 +
  35.755 +        context = DummyImportContext( site )
  35.756 +        context._files[ TOOLSET_XML ] = _FORBIDDEN_TOOLSET_XML
  35.757 +
  35.758 +        importToolset( context )
  35.759 +
  35.760 +        self.assertEqual( len( site.objectIds() ), 2 )
  35.761 +        self.failUnless( 'portal_setup' in site.objectIds() )
  35.762 +        self.failUnless( 'saved' in site.objectIds() )
  35.763 +
  35.764 +    def test_required_tools_missing( self ):
  35.765 +
  35.766 +        from Products.CMFSetup.tool import TOOLSET_XML
  35.767 +        from Products.CMFSetup.tool import importToolset
  35.768 +
  35.769 +        site = self._initSite()
  35.770 +        self.assertEqual( len( site.objectIds() ), 1 )
  35.771 +
  35.772 +        context = DummyImportContext( site )
  35.773 +        context._files[ TOOLSET_XML ] = _REQUIRED_TOOLSET_XML
  35.774 +
  35.775 +        importToolset( context )
  35.776 +
  35.777 +        self.assertEqual( len( site.objectIds() ), 3 )
  35.778 +        self.failUnless( isinstance( aq_base( site._getOb( 'mandatory' ) )
  35.779 +                                   , DummyTool ) )
  35.780 +        self.failUnless( isinstance( aq_base( site._getOb( 'obligatory' ) )
  35.781 +                                   , DummyTool ) )
  35.782 +
  35.783 +    def test_required_tools_no_replacement( self ):
  35.784 +
  35.785 +        from Products.CMFSetup.tool import TOOLSET_XML
  35.786 +        from Products.CMFSetup.tool import importToolset
  35.787 +
  35.788 +        site = self._initSite()
  35.789 +
  35.790 +        mandatory = DummyTool()
  35.791 +        mandatory._setId( 'mandatory' )
  35.792 +        site._setObject( 'mandatory', mandatory )
  35.793 +
  35.794 +        obligatory = DummyTool()
  35.795 +        obligatory._setId( 'obligatory' )
  35.796 +        site._setObject( 'obligatory', obligatory )
  35.797 +
  35.798 +        self.assertEqual( len( site.objectIds() ), 3 )
  35.799 +
  35.800 +        context = DummyImportContext( site )
  35.801 +        context._files[ TOOLSET_XML ] = _REQUIRED_TOOLSET_XML
  35.802 +
  35.803 +        importToolset( context )
  35.804 +
  35.805 +        self.assertEqual( len( site.objectIds() ), 3 )
  35.806 +        self.failUnless( aq_base( site._getOb( 'mandatory' ) ) is mandatory )
  35.807 +        self.failUnless( aq_base( site._getOb( 'obligatory' ) ) is obligatory )
  35.808 +
  35.809 +    def test_required_tools_with_replacement( self ):
  35.810 +
  35.811 +        from Products.CMFSetup.tool import TOOLSET_XML
  35.812 +        from Products.CMFSetup.tool import importToolset
  35.813 +
  35.814 +        site = self._initSite()
  35.815 +
  35.816 +        mandatory = AnotherDummyTool()
  35.817 +        mandatory._setId( 'mandatory' )
  35.818 +        site._setObject( 'mandatory', mandatory )
  35.819 +
  35.820 +        obligatory = AnotherDummyTool()
  35.821 +        obligatory._setId( 'obligatory' )
  35.822 +        site._setObject( 'obligatory', obligatory )
  35.823 +
  35.824 +        self.assertEqual( len( site.objectIds() ), 3 )
  35.825 +
  35.826 +        context = DummyImportContext( site )
  35.827 +        context._files[ TOOLSET_XML ] = _REQUIRED_TOOLSET_XML
  35.828 +
  35.829 +        importToolset( context )
  35.830 +
  35.831 +        self.assertEqual( len( site.objectIds() ), 3 )
  35.832 +
  35.833 +        self.failIf( aq_base( site._getOb( 'mandatory' ) ) is mandatory )
  35.834 +        self.failUnless( isinstance( aq_base( site._getOb( 'mandatory' ) )
  35.835 +                                   , DummyTool ) )
  35.836 +
  35.837 +        self.failIf( aq_base( site._getOb( 'obligatory' ) ) is obligatory )
  35.838 +        self.failUnless( isinstance( aq_base( site._getOb( 'obligatory' ) )
  35.839 +                                   , DummyTool ) )
  35.840 +
  35.841 +
  35.842 +class DummyTool( Folder ):
  35.843 +
  35.844 +    pass
  35.845 +
  35.846 +class AnotherDummyTool( Folder ):
  35.847 +
  35.848 +    pass
  35.849 +
  35.850 +_EMPTY_TOOLSET_XML = """\
  35.851 +<?xml version="1.0"?>
  35.852 +<tool-setup>
  35.853 +</tool-setup>
  35.854 +"""
  35.855 +
  35.856 +_NORMAL_TOOLSET_XML = """\
  35.857 +<?xml version="1.0" ?>
  35.858 +<tool-setup>
  35.859 +<forbidden tool_id="doomed"/>
  35.860 +<required class="path.to.one" tool_id="mandatory"/>
  35.861 +<required class="path.to.another" tool_id="obligatory"/>
  35.862 +</tool-setup>
  35.863 +"""
  35.864 +
  35.865 +_FORBIDDEN_TOOLSET_XML = """\
  35.866 +<?xml version="1.0"?>
  35.867 +<tool-setup>
  35.868 + <forbidden tool_id="doomed" />
  35.869 + <forbidden tool_id="damned" />
  35.870 + <forbidden tool_id="blasted" />
  35.871 +</tool-setup>
  35.872 +"""
  35.873 +
  35.874 +_REQUIRED_TOOLSET_XML = """\
  35.875 +<?xml version="1.0"?>
  35.876 +<tool-setup>
  35.877 + <required
  35.878 +    tool_id="mandatory"
  35.879 +    class="Products.CMFSetup.tests.test_tool.DummyTool" />
  35.880 + <required
  35.881 +    tool_id="obligatory"
  35.882 +    class="Products.CMFSetup.tests.test_tool.DummyTool" />
  35.883 +</tool-setup>
  35.884 +"""
  35.885 +
  35.886 +def test_suite():
  35.887 +    # reimport to make sure tests are run from Products
  35.888 +    from Products.CMFSetup.tests.test_tool import SetupToolTests
  35.889 +    from Products.CMFSetup.tests.test_tool import Test_exportToolset
  35.890 +    from Products.CMFSetup.tests.test_tool import Test_importToolset
  35.891 +
  35.892 +    return unittest.TestSuite((
  35.893 +        unittest.makeSuite( SetupToolTests ),
  35.894 +        unittest.makeSuite( Test_exportToolset ),
  35.895 +        unittest.makeSuite( Test_importToolset ),
  35.896 +        ))
  35.897 +
  35.898 +if __name__ == '__main__':
  35.899 +    unittest.main(defaultTest='test_suite')
    36.1 new file mode 100644
    36.2 --- /dev/null
    36.3 +++ b/tests/test_typeinfo.py
    36.4 @@ -0,0 +1,903 @@
    36.5 +##############################################################################
    36.6 +#
    36.7 +# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
    36.8 +#
    36.9 +# This software is subject to the provisions of the Zope Public License,
   36.10 +# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
   36.11 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   36.12 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   36.13 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   36.14 +# FOR A PARTICULAR PURPOSE.
   36.15 +#
   36.16 +##############################################################################
   36.17 +""" Unit tests for type information export import
   36.18 +
   36.19 +$Id: test_typeinfo.py 37135 2005-07-08 13:24:33Z tseaver $
   36.20 +"""
   36.21 +
   36.22 +import unittest
   36.23 +import Testing
   36.24 +try:
   36.25 +    import Zope2
   36.26 +except ImportError: # BBB: for Zope 2.7
   36.27 +    import Zope as Zope2
   36.28 +Zope2.startup()
   36.29 +
   36.30 +from OFS.Folder import Folder
   36.31 +from OFS.SimpleItem import SimpleItem
   36.32 +
   36.33 +from Products.CMFCore.TypesTool import FactoryTypeInformation
   36.34 +from Products.CMFCore.TypesTool import ScriptableTypeInformation
   36.35 +from Products.CMFCore.permissions import View
   36.36 +from Products.CMFCore.permissions import AccessContentsInformation
   36.37 +from Products.CMFCore.permissions import ModifyPortalContent
   36.38 +
   36.39 +from common import BaseRegistryTests
   36.40 +from common import DummyExportContext
   36.41 +from common import DummyImportContext
   36.42 +
   36.43 +
   36.44 +class DummyTypeInfo(SimpleItem):
   36.45 +
   36.46 +    pass
   36.47 +
   36.48 +
   36.49 +class DummyTypesTool(Folder):
   36.50 +
   36.51 +    def __init__(self, type_infos):
   36.52 +
   36.53 +        self._type_infos = type_infos
   36.54 +        for id, obj in [(x['id'], DummyTypeInfo(x))
   36.55 +                        for x in type_infos]:
   36.56 +            self._setObject(id, obj)
   36.57 +
   36.58 +    def listContentTypes(self):
   36.59 +
   36.60 +        return [x['id'] for x in self._type_infos]
   36.61 +
   36.62 +    def getTypeInfo(self, id):
   36.63 +
   36.64 +        info = [x for x in self._type_infos if x['id'] == id]
   36.65 +        if len(info) == 0:
   36.66 +            raise KeyError, id
   36.67 +        info = info[0]
   36.68 +
   36.69 +        if 'product' in info.keys():
   36.70 +            return FactoryTypeInformation(**info)
   36.71 +        else:
   36.72 +            return ScriptableTypeInformation(**info)
   36.73 +
   36.74 +class _TypeInfoSetup(BaseRegistryTests):
   36.75 +
   36.76 +    def _initSite(self, type_infos=()):
   36.77 +
   36.78 +        self.root.site = Folder(id='site')
   36.79 +        self.root.site.portal_types = DummyTypesTool(type_infos)
   36.80 +        return self.root.site
   36.81 +
   36.82 +
   36.83 +class TypesToolExportConfiguratorTests(_TypeInfoSetup):
   36.84 +
   36.85 +    def _getTargetClass(self):
   36.86 +
   36.87 +        from Products.CMFSetup.typeinfo import TypesToolExportConfigurator
   36.88 +        return TypesToolExportConfigurator
   36.89 +
   36.90 +    def test_listTypeInfo_empty(self):
   36.91 +
   36.92 +        site = self._initSite()
   36.93 +        configurator = self._makeOne(site).__of__(site)
   36.94 +
   36.95 +        self.assertEqual(len(configurator.listTypeInfo()), 0)
   36.96 +
   36.97 +    def test_listTypeInfo_filled (self):
   36.98 +
   36.99 +        site = self._initSite(_TI_LIST)
  36.100 +        configurator = self._makeOne(site).__of__(site)
  36.101 +
  36.102 +        self.assertEqual(len(configurator.listTypeInfo()), len(_TI_LIST))
  36.103 +
  36.104 +        info_list = configurator.listTypeInfo()
  36.105 +        self.assertEqual(len(info_list), len(_TI_LIST))
  36.106 +
  36.107 +        _marker = object()
  36.108 +
  36.109 +        for i in range(len(_TI_LIST)):
  36.110 +            found = info_list[i]
  36.111 +            expected = _TI_LIST[i]
  36.112 +            self.assertEqual(found['id'], expected['id'])
  36.113 +            self.failUnless(found.get('filename', _marker) is _marker)
  36.114 +
  36.115 +    def test_listTypeInfo_with_filename (self):
  36.116 +
  36.117 +        site = self._initSite(_TI_LIST_WITH_FILENAME)
  36.118 +        configurator = self._makeOne(site).__of__(site)
  36.119 +
  36.120 +        info_list = configurator.listTypeInfo()
  36.121 +        self.assertEqual(len(info_list), len(_TI_LIST_WITH_FILENAME))
  36.122 +
  36.123 +        for i in range(len(_TI_LIST_WITH_FILENAME)):
  36.124 +            found = info_list[i]
  36.125 +            expected = _TI_LIST_WITH_FILENAME[i]
  36.126 +            self.assertEqual(found['id'], expected['id'])
  36.127 +            self.assertEqual(found['filename'],
  36.128 +                             'types/%s.xml'
  36.129 +                             % expected['id'].replace(' ', '_')
  36.130 +                             )
  36.131 +
  36.132 +    def test_generateXML_empty(self):
  36.133 +
  36.134 +        site = self._initSite()
  36.135 +        configurator = self._makeOne(site).__of__(site)
  36.136 +        self._compareDOM(configurator.generateXML(), _EMPTY_TOOL_EXPORT)
  36.137 +
  36.138 +    def test_generateXML_normal(self):
  36.139 +
  36.140 +        site = self._initSite(_TI_LIST)
  36.141 +        configurator = self._makeOne(site).__of__(site)
  36.142 +        self._compareDOM(configurator.generateXML(), _NORMAL_TOOL_EXPORT)
  36.143 +
  36.144 +    def test_generateXML_explicit_filename(self):
  36.145 +
  36.146 +        site = self._initSite(_TI_LIST_WITH_FILENAME)
  36.147 +        configurator = self._makeOne(site).__of__(site)
  36.148 +        self._compareDOM(configurator.generateXML(), _FILENAME_EXPORT)
  36.149 +
  36.150 +
  36.151 +class TypesToolImportConfiguratorTests(_TypeInfoSetup):
  36.152 +
  36.153 +    def _getTargetClass(self):
  36.154 +
  36.155 +        from Products.CMFSetup.typeinfo import TypesToolImportConfigurator
  36.156 +        return TypesToolImportConfigurator
  36.157 +
  36.158 +    def test_parseXML_empty(self):
  36.159 +
  36.160 +        site = self._initSite()
  36.161 +        configurator = self._makeOne(site).__of__(site)
  36.162 +
  36.163 +        tool_info = configurator.parseXML(_EMPTY_TOOL_EXPORT)
  36.164 +        self.assertEqual(len(tool_info['types']), 0)
  36.165 +
  36.166 +    def test_parseXML_normal(self):
  36.167 +
  36.168 +        site = self._initSite()
  36.169 +        configurator = self._makeOne(site).__of__(site)
  36.170 +
  36.171 +        tool_info = configurator.parseXML(_NORMAL_TOOL_EXPORT)
  36.172 +        self.assertEqual(len(tool_info['types']), 2)
  36.173 +
  36.174 +        type_info = tool_info['types'][0]
  36.175 +        self.assertEqual(type_info['id'], 'foo')
  36.176 +        self.assertEqual(type_info['filename'], 'types/foo.xml')
  36.177 +        type_info = tool_info['types'][1]
  36.178 +        self.assertEqual(type_info['id'], 'bar')
  36.179 +        self.assertEqual(type_info['filename'], 'types/bar.xml')
  36.180 +
  36.181 +    def test_parseXML_with_filename(self):
  36.182 +
  36.183 +        site = self._initSite()
  36.184 +        configurator = self._makeOne(site).__of__(site)
  36.185 +
  36.186 +        tool_info = configurator.parseXML(_FILENAME_EXPORT)
  36.187 +        self.assertEqual(len(tool_info['types']), 2)
  36.188 +
  36.189 +        type_info = tool_info['types'][0]
  36.190 +        self.assertEqual(type_info['id'], 'foo object')
  36.191 +        self.assertEqual(type_info['filename'], 'types/foo_object.xml')
  36.192 +        type_info = tool_info['types'][1]
  36.193 +        self.assertEqual(type_info['id'], 'bar object')
  36.194 +        self.assertEqual(type_info['filename'], 'types/bar_object.xml')
  36.195 +
  36.196 +
  36.197 +class TypeInfoExportConfiguratorTests(_TypeInfoSetup):
  36.198 +
  36.199 +    def _getTargetClass(self):
  36.200 +
  36.201 +        from Products.CMFSetup.typeinfo import TypeInfoExportConfigurator
  36.202 +        return TypeInfoExportConfigurator
  36.203 +
  36.204 +    def test_getTypeInfo_nonesuch(self):
  36.205 +
  36.206 +        site = self._initSite(_TI_LIST)
  36.207 +        configurator = self._makeOne(site).__of__(site)
  36.208 +
  36.209 +        self.assertRaises(ValueError, configurator.getTypeInfo, 'qux')
  36.210 +
  36.211 +    def test_getTypeInfo_FTI(self):
  36.212 +
  36.213 +        site = self._initSite(_TI_LIST)
  36.214 +        configurator = self._makeOne(site).__of__(site)
  36.215 +        found = configurator.getTypeInfo('foo')
  36.216 +        expected = _TI_LIST[0]
  36.217 +
  36.218 +        self.assertEqual(found['kind'], 'Factory-based Type Information')
  36.219 +
  36.220 +        for key in ('id', 'aliases'):
  36.221 +            self.assertEqual(found[key], expected[key])
  36.222 +