testERP5Core.py 21.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
##############################################################################
#
# Copyright (c) 2004, 2005, 2006 Nexedi SARL and Contributors. 
# All Rights Reserved.
#          Romain Courteaud <romain@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################

30
import unittest
Jérome Perrin's avatar
Jérome Perrin committed
31
import md5
32
import pprint
33

34
import transaction
35
from AccessControl.SecurityManagement import newSecurityManager
36
from Testing import ZopeTestCase
37

38
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
39
from Products.ERP5Type.tests.utils import DummyTranslationService
40

41 42 43 44 45 46 47 48 49 50 51 52 53 54
try:
  # BACK: Zope 2.8
  # When we drop support, refactor the tests to use the i18n utilities
  # directly
  from Products.PageTemplates.GlobalTranslationService import \
                                setGlobalTranslationService
  def unregister_translation_domain_fallback():
    """dummy function to keep the beforeTearDown() code neat"""
 
except ImportError:
  # Zope 2.12, simulate setting the globalTranslationService with
  # zope.i18n utilities
  import zope.interface
  import zope.component
55 56
  import Acquisition

57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
  global_translation_service = None
  
  from zope.i18n.interfaces import ITranslationDomain, \
                                   IFallbackTranslationDomainFactory
  class DummyTranslationDomainFallback(object):
    zope.interface.implements(ITranslationDomain)
    zope.interface.classProvides(IFallbackTranslationDomainFactory)
    
    def __init__(self, domain):
      self.domain = domain
    
    def translate(self, msgid, mapping=None, *args, **kw):
      return global_translation_service.translate(self.domain, msgid, mapping,
                                                  *args, **kw)
  
  def setGlobalTranslationService(translation_service):
    global global_translation_service
    global_translation_service = translation_service
    zope.component.provideUtility(DummyTranslationDomainFallback,
                                  provides=IFallbackTranslationDomainFactory)
77 78 79 80 81 82 83 84 85
    # disable translation for the 'ui' domain so it can use the fallback above.
    # Save it on a portal attribute since we don't have access to the test
    # class 
    sm = zope.component.getSiteManager()
    portal = Acquisition.aq_parent(sm)
    ui_domain = sm.getUtility(ITranslationDomain, name='ui')
    # store in a list to avoid acquisition wrapping
    portal._save_ui_domain = [ui_domain]
    sm.unregisterUtility(provided=ITranslationDomain, name='ui')
86 87 88 89

  def unregister_translation_domain_fallback():
    from zope.component.globalregistry import base
    base.unregisterUtility(DummyTranslationDomainFallback)
90 91 92 93 94 95
    sm = zope.component.getSiteManager()
    portal = Acquisition.aq_parent(sm)
    ui_domain = getattr(portal, '_save_ui_domain', [None]).pop()
    if ui_domain is not None:
      sm.registerUtility(ui_domain, ITranslationDomain, 'ui')
      del portal._save_ui_domain
96

97 98 99 100 101 102 103
HTTP_OK = 200
HTTP_UNAUTHORIZED = 401
HTTP_REDIRECT = 302

class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
  """Test for erp5_core business template.
  """
104 105
  run_all_test = 1
  quiet = 1
106

107
  manager_username = 'rc'
108
  manager_password = 'w'
109 110 111 112

  def getTitle(self):
    return "ERP5Core"

113 114 115 116 117
  def getBusinessTemplateList(self):
    """
    """
    return ('erp5_base', )

118 119
  def login(self, quiet=0, run=run_all_test):
    uf = self.getPortal().acl_users
120 121
    uf._doAddUser(self.manager_username, self.manager_password, ['Manager'], [])
    user = uf.getUserById(self.manager_username).__of__(uf)
122 123
    newSecurityManager(None, user)

124
  def afterSetUp(self):
125
    self.login()
126 127
    self.portal_id = self.portal.getId()
    self.auth = '%s:%s' % (self.manager_username, self.manager_password)
128

Jérome Perrin's avatar
Jérome Perrin committed
129
  def beforeTearDown(self):
130
    transaction.abort()
131
    unregister_translation_domain_fallback()
Jérome Perrin's avatar
Jérome Perrin committed
132 133 134
    if 'test_folder' in self.portal.objectIds():
      self.portal.manage_delObjects(['test_folder'])
    self.portal.portal_selections.setSelectionFor('test_selection', None)
135
    transaction.commit()
Jérome Perrin's avatar
Jérome Perrin committed
136 137
    self.tic()

138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
  def test_01_ERP5Site_createModule(self, quiet=quiet, run=run_all_test):
    """
      Test that a module is created when ERP5Site_createModule is given the
      strict minimum number of arguments.
      A created module is composed of :
       - the module itself, directly in the portal object
       - a skin folder, directly in the skins tool
       - a portal type for the module
       - a portal type for the objects which can be contained in the module

      TODO: check more behaviours of the creation script, like skin priority, ...
    """
    if not run: return

    module_portal_type='UnitTest Module'
    portal_skins_folder='erp5_unittest'
    object_portal_type='UnitTest'
    object_title='UnitTest'
    module_id='unittest_module'
    module_title='UnitTests'
    
159 160 161
    
    skins_tool = self.portal.portal_skins
    types_tool = self.portal.portal_types
162
    self.failIf(self.portal._getOb(module_id, None) is not None)
163 164 165 166
    self.assertEqual(skins_tool._getOb(portal_skins_folder, None), None)
    self.assertEqual(types_tool._getOb(module_portal_type, None), None)
    self.assertEqual(types_tool._getOb(object_portal_type, None), None)

167 168 169 170 171 172
    self.portal.ERP5Site_createModule(module_portal_type=module_portal_type,
                                      portal_skins_folder=portal_skins_folder,
                                      object_portal_type=object_portal_type,
                                      object_title=object_title,
                                      module_id=module_id,
                                      module_title=module_title)
173 174 175
    self.failUnless(self.portal._getOb(module_id, None) is not None)
    self.assertEquals(module_title,
                      self.portal._getOb(module_id).getTitle())
176 177
    self.assertNotEqual(types_tool.getTypeInfo(module_portal_type), None)
    self.assertNotEqual(types_tool.getTypeInfo(object_portal_type), None)
178 179
    self.assertTrue(module_portal_type
                      in self.portal.getPortalModuleTypeList())
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
    
    skin_folder = skins_tool._getOb(portal_skins_folder, None)
    self.assertNotEqual(skin_folder, None)
    self.assert_('UnitTest_view' in skin_folder.objectIds())
    view_form = skin_folder.UnitTest_view
    self.assertEquals('form_view', view_form.pt)
    self.assertEquals('Base_edit', view_form.action)
    
    self.assert_('UnitTestModule_viewUnitTestList' in skin_folder.objectIds())
    list_form = skin_folder.UnitTestModule_viewUnitTestList
    self.assertEquals('form_list', list_form.pt)
    self.assertEquals('Base_doSelect', list_form.action)
    self.assert_('listbox' in [x.getId() for x in list_form.get_fields()])
    self.assert_('listbox' in
            [x.getId() for x in list_form.get_fields_in_group('bottom')])

    # make sure we can use our module
    self.portal.unittest_module.view()
    self.portal.unittest_module.newContent(id='document', portal_type='UnitTest')
    self.portal.unittest_module.document.view()

201 202 203 204 205
    # make sure translation domains are set correctly
    self.assertEquals('erp5_ui',
        self.portal.unittest_module.getTitleTranslationDomain())
    self.assertEquals('erp5_ui',
        self.portal.unittest_module.getShortTitleTranslationDomain())
206 207

    type_information = self.portal.portal_types[module_portal_type]
208 209 210
    self.assertTrue('business_application'
                    in type_information.getTypeBaseCategoryList())

211 212 213 214 215 216 217 218 219 220 221
  def check_actions(self, target, expected):
    actions = self.portal.portal_actions.listFilteredActionsFor(target)
    got = {}
    for category, actions in actions.items():
      got[category] = [dict(title=action['title'], id=action['id'])
                       for action in actions
                       if action['visible']]
    msg = ("Actions do not match. Expected:\n%s\n\nGot:\n%s\n" %
           (pprint.pformat(expected), pprint.pformat(got))) 
    self.assertEquals(expected, got, msg)

222
  def test_manager_actions_on_portal(self):
223 224 225 226 227 228 229 230 231 232 233 234
    # as manager:
    expected = {'folder': [],
                'global': [{'title': 'Manage Business Templates',
                            'id': 'bt_tool'},
                           {'title': 'Configure Categories',
                            'id': 'category_tool'},
                           {'title': 'Create Module',
                            'id': 'create_module'},
                           {'title': 'Configure Portal Types',
                            'id': 'types_tool'},
                           {'title': 'Undo', 'id': 'undo'}],
                'object': [],
235 236
                'object_action': [{'id': 'post_query', 'title': 'Post a Query'}],
                'object_jump': [{'id': 'jump_query', 'title': 'Queries'}],
237 238 239 240 241 242 243 244 245 246
                'object_search': [{'title': 'Search', 'id': 'search'}],
                'object_sort': [{'title': 'Sort', 'id': 'sort_on'}],
                'object_ui': [{'title': 'Modify UI', 'id': 'list_ui'}],
                'object_view': [{'title': 'History', 'id': 'history'},
                                {'title': 'Metadata', 'id': 'metadata'}],
                'user': [{'title': 'Preferences', 'id': 'preferences'},
                         {'title': 'Log out', 'id': 'logout'}],
                'workflow': []}
    self.check_actions(self.portal, expected)

247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
  def test_member_actions_on_portal(self):
    # as Member
    self.createUser('usual_member')
    self.logout()
    transaction.commit()
    self.tic()
    ERP5TypeTestCase.login(self, 'usual_member')
    expected = {'folder': [],
                'global': [],
                'object': [],
                'object_search': [{'title': 'Search', 'id': 'search'}],
                'object_sort': [{'title': 'Sort', 'id': 'sort_on'}],
                'object_ui': [{'title': 'Modify UI', 'id': 'list_ui'}],
                'object_view': [{'title': 'History', 'id': 'history'}],
                'user': [{'title': 'Preferences', 'id': 'preferences'},
                         {'title': 'Log out', 'id': 'logout'}],
                'workflow': []}
    self.check_actions(self.portal, expected)

  def test_anonymous_actions_on_portal(self):
267 268 269 270 271 272 273 274 275 276 277 278
    # as anonymous:
    self.logout()
    expected = {'folder': [],
                'global': [],
                'object': [],
                'object_search': [{'title': 'Search', 'id': 'search'}],
                'object_sort': [{'title': 'Sort', 'id': 'sort_on'}],
                'object_ui': [{'title': 'Modify UI', 'id': 'list_ui'}],
                'object_view': [{'title': 'History', 'id': 'history'}],
                'user': [{'id': 'login', 'title': 'Login'}],
                'workflow': []}
    self.check_actions(self.portal, expected)
279

280 281 282 283 284 285 286 287 288 289 290 291 292 293
  def test_frontpage(self):
    """Test we can view the front page.
    """
    response = self.publish('%s/view' % self.portal_id, self.auth)
    self.assertEquals(HTTP_OK, response.getStatus())
    
  def test_login_form(self):
    """Test anonymous user are redirected to login_form
    """
    response = self.publish('%s/view' % self.portal_id)
    self.assertEquals(HTTP_REDIRECT, response.getStatus())
    self.assertEquals('%s/login_form' % self.portal.absolute_url(),
                      response.getHeader('Location'))

294 295 296 297 298 299 300 301 302
  def test_view_tools(self):
    """Test we can view tools."""
    for tool in ('portal_categories',
                 'portal_templates',
                 'portal_rules',
                 'portal_alarms',):
      response = self.publish('%s/%s/view' % (self.portal_id, tool), self.auth)
      self.assertEquals(HTTP_OK, response.getStatus(),
                        "%s: %s" % (tool, response.getStatus()))
303 304 305 306 307
 
  def test_allowed_content_types_translated(self):
    """Tests allowed content types from the action menu are translated"""
    translation_service = DummyTranslationService()
    setGlobalTranslationService(translation_service)
308 309 310
    # assumes that we can add Business Template in template tool
    response = self.publish('%s/portal_templates/view' %
                                self.portal_id, self.auth)
311
    self.assertEquals(HTTP_OK, response.getStatus())
312 313 314 315 316
    self.failUnless(('Business Template', {})
                    in translation_service._translated['ui'])
    self.failUnless(
      ('Add ${portal_type}', {'portal_type': 'Business Template'}) in
      translation_service._translated['ui'])
317 318 319 320 321

  def test_jump_action_translated(self):
    """Tests jump actions are translated"""
    translation_service = DummyTranslationService()
    setGlobalTranslationService(translation_service)
322
    # adds a new jump action to Template Tool portal type
Julien Muchembled's avatar
Julien Muchembled committed
323 324 325 326 327 328
    self.getTypesTool().getTypeInfo('Template Tool').newContent(
                      portal_type='Action Information',
                      reference='dummy_jump_action',
                      title='Dummy Jump Action',
                      action_permission='View',
                      action_type='object_jump')
329 330
    response = self.publish('%s/portal_templates/view' %
                            self.portal_id, self.auth)
331 332 333 334
    self.assertEquals(HTTP_OK, response.getStatus())
    self.failUnless(('Dummy Jump Action', {}) in
                      translation_service._translated['ui'])

335 336
  def test_error_log(self):
    self.failUnless('error_log' in self.portal.objectIds())
337 338 339 340
    self.failUnless(self.portal.error_log.getProperties()['copy_to_zlog'])
    self.failIf('Unauthorized' in
                self.portal.error_log.getProperties()['ignored_exceptions'])

341 342 343 344 345 346 347 348 349 350 351 352 353 354
  def test_03_getDefaultModule(self, quiet=quiet, run=run_all_test):
    """
    test getDefaultModule method
    """
    if not run: 
      return
    portal_id = self.getPortal().getId()
    object_portal_type = ' '.join([part.capitalize() for part \
                                    in portal_id.split('_')])
    module_portal_type='%s Module' % object_portal_type
    portal_skins_folder='erp5_unittest'
    object_title=object_portal_type
    module_id="%s_module" % portal_id
    module_title='%ss' % object_portal_type
355
    
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
    # Create module for testing
    self.failIf(self.portal._getOb(module_id, None) is not None)
    self.assertEqual(self.portal.portal_skins._getOb(portal_skins_folder, None),
                     None)
    self.assertEqual(self.portal.portal_types.getTypeInfo(module_portal_type),
                     None)
    self.assertEqual(self.portal.portal_types.getTypeInfo(object_portal_type),
                     None)
    self.portal.ERP5Site_createModule(module_portal_type=module_portal_type,
                                      portal_skins_folder=portal_skins_folder,
                                      object_portal_type=object_portal_type,
                                      object_title=object_title,
                                      module_id=module_id,
                                      module_title=module_title)

    # Test
    self.assertEquals(
        module_id,
        self.portal.getDefaultModule(object_portal_type).getId())
    self.assertEquals(
        module_portal_type,
        self.portal.getDefaultModule(object_portal_type).getPortalType())
    self.assertEquals(
        module_id,
        self.portal.getDefaultModule(module_portal_type).getId())
    self.assertEquals(
        module_portal_type,
        self.portal.getDefaultModule(module_portal_type).getPortalType())

385 386 387 388
  def test_catalog_with_very_long_login_name(self, quiet=quiet, run=run_all_test):
    """Make sure that user with very long login name can find his document by catalog"""
    portal = self.getPortal()
    # Create user account with very long login name
389 390 391 392
    login_name = 'very_very_looooooooooooooooooooooooooooooooooooooooooooooooooooo' + \
    'oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong_login_name'
    password = 'very_long_passworddddddddddddddddddddddddddddddddddddddddddddddddd' + \
    'ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'
393 394 395 396 397
    acl_users = portal.acl_users
    acl_users._doAddUser(login_name, password, ['Member'], [])
    user = acl_users.getUserById(login_name).__of__(acl_users)
    # Login as the above user
    newSecurityManager(None, user)
398
    self.auth = '%s:%s' % (login_name, password)
399
    transaction.commit()
400 401 402 403

    # Create preference
    portal.portal_preferences.newContent('Preference', title='My Test Preference')

404
    transaction.commit()
405 406 407 408 409 410
    self.tic()

    self.assertEqual(
      len(portal.portal_catalog(portal_type='Preference',
                                title='My Test Preference')),
      1)
411 412
    response = self.publish('%s/view' % self.portal_id, self.auth)
    self.assertEquals(HTTP_OK, response.getStatus())
413

Jérome Perrin's avatar
Jérome Perrin committed
414 415 416 417 418 419 420
  def test_Folder_delete(self):
    module = self.portal.newContent(portal_type='Folder', id='test_folder')
    document_1 = module.newContent(portal_type='Folder', id='1')
    document_2 = module.newContent(portal_type='Folder', id='2')
    uid_list = [document_1.getUid(), document_2.getUid()]
    self.portal.portal_selections.setSelectionParamsFor(
          'test_selection', dict(uids=uid_list))
421
    transaction.commit()
Jérome Perrin's avatar
Jérome Perrin committed
422 423 424 425 426 427
    self.tic()
    md5_string = md5.new(str(sorted([str(x) for x in uid_list]))).hexdigest()
    redirect = module.Folder_delete(selection_name='test_selection',
                                    uids=uid_list,
                                    md5_object_uid_list=md5_string)
    self.assert_('Deleted.' in redirect, redirect)
428
    transaction.savepoint(optimistic=True)
Jérome Perrin's avatar
Jérome Perrin committed
429 430 431 432
    self.assertEquals(len(module.objectValues()), 0)

  def test_Folder_delete_related_object(self):
    # deletion is refused if there are related objects
433 434 435 436 437 438 439
    organisation_module_len = len(self.portal.organisation_module)
    person_module_len = len(self.portal.person_module)
    organisation = self.portal.organisation_module.newContent()
    person = self.portal.person_module.newContent(
      default_career_subordination_value=organisation)
    for obj in person, organisation:
      obj.manage_addLocalRoles(self.manager_username, ['Assignor'])
440
    transaction.commit()
441
    self.assertEqual(0, organisation.getRelationCountForDeletion())
Jérome Perrin's avatar
Jérome Perrin committed
442
    self.tic()
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
    self.assertEqual(2, organisation.getRelationCountForDeletion())
    self.assertEqual(0, person.getRelationCountForDeletion())
    def delete(assert_deleted, obj):
      uid_list = [obj.getUid()]
      self.portal.portal_selections.setSelectionParamsFor(
                          'test_selection', dict(uids=uid_list))
      md5_string = md5.new(str(sorted(str(x) for x in uid_list))).hexdigest()
      redirect = obj.getParentValue().Folder_delete(uids=uid_list,
        selection_name='test_selection', md5_object_uid_list=md5_string)
      self.assertTrue(('Sorry, 1 item is in use.', 'Deleted.')[assert_deleted]
                      in redirect, redirect)
      transaction.commit()
      self.tic()
    delete(0, organisation)
    delete(1, person)
    self.assertEqual(0, organisation.getRelationCountForDeletion())
    delete(1, organisation)
    self.assertEquals(organisation_module_len + 1,
                      len(self.portal.organisation_module))
    self.assertEquals(person_module_len + 1,
                      len(self.portal.person_module))
Jérome Perrin's avatar
Jérome Perrin committed
464 465 466 467 468 469 470 471 472 473 474 475 476 477

  def test_Folder_delete_non_accessible_object(self):
    # deletion is refused if there are related objects, even if those related
    # objects cannot be accessed
    module = self.portal.newContent(portal_type='Folder', id='test_folder')
    document_1 = module.newContent(portal_type='Folder', id='1')
    document_2 = module.newContent(portal_type='Folder', id='2')
    self.portal.portal_categories.setCategoryMembership(
                                context=document_1,
                                base_category_list=('source',),
                                category_list=document_2.getRelativeUrl())
    uid_list = [document_2.getUid(), ]
    self.portal.portal_selections.setSelectionParamsFor(
                          'test_selection', dict(uids=uid_list))
478
    transaction.commit()
Jérome Perrin's avatar
Jérome Perrin committed
479 480 481 482 483 484 485 486 487 488 489
    self.tic()
    self.assertEquals([document_1],
        self.portal.portal_categories.getRelatedValueList(document_2))
    md5_string = md5.new(str(sorted([str(x) for x in uid_list]))).hexdigest()

    document_1.manage_permission('View', [], acquire=0)
    document_1.manage_permission('Access contents information', [], acquire=0)
    redirect = module.Folder_delete(selection_name='test_selection',
                                    uids=uid_list,
                                    md5_object_uid_list=md5_string)
    self.assert_('Sorry, 1 item is in use.' in redirect, redirect)
490
    transaction.savepoint(optimistic=True)
Jérome Perrin's avatar
Jérome Perrin committed
491 492
    self.assertEquals(len(module.objectValues()), 2)

493 494 495 496 497 498
  def test_getPropertyForUid(self):
    error_list = []
    for i in self.portal.objectValues():
      if i.getUid() != i.getProperty('uid'):
        error_list.append((i.getId(), i.getUid(), i.getProperty('uid')))
    self.assertEquals(error_list, [])
Jérome Perrin's avatar
Jérome Perrin committed
499

500 501 502 503
def test_suite():
  suite = unittest.TestSuite()
  suite.addTest(unittest.makeSuite(TestERP5Core))
  return suite
504