testERP5Core.py 22.4 KB
Newer Older
1 2
##############################################################################
#
3
# Copyright (c) 2004, 2005, 2006 Nexedi SARL and Contributors.
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
# 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
31
from hashlib import md5
32
import pprint
33

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

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

40
if 1: # BBB
41 42 43 44
  # Zope 2.12, simulate setting the globalTranslationService with
  # zope.i18n utilities
  import zope.interface
  import zope.component
45 46
  import Acquisition

47
  global_translation_service = None
48

49 50 51 52 53
  from zope.i18n.interfaces import ITranslationDomain, \
                                   IFallbackTranslationDomainFactory
  class DummyTranslationDomainFallback(object):
    zope.interface.implements(ITranslationDomain)
    zope.interface.classProvides(IFallbackTranslationDomainFactory)
54

55 56
    def __init__(self, domain):
      self.domain = domain
57

58 59 60
    def translate(self, msgid, mapping=None, *args, **kw):
      return global_translation_service.translate(self.domain, msgid, mapping,
                                                  *args, **kw)
61

62 63 64 65 66
  def setGlobalTranslationService(translation_service):
    global global_translation_service
    global_translation_service = translation_service
    zope.component.provideUtility(DummyTranslationDomainFallback,
                                  provides=IFallbackTranslationDomainFactory)
67 68
    # 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
69
    # class
70 71 72 73 74 75
    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')
76 77 78 79

  def unregister_translation_domain_fallback():
    from zope.component.globalregistry import base
    base.unregisterUtility(DummyTranslationDomainFallback)
80 81 82 83
    sm = zope.component.getSiteManager()
    portal = Acquisition.aq_parent(sm)
    ui_domain = getattr(portal, '_save_ui_domain', [None]).pop()
    if ui_domain is not None:
84 85
      # aq_base() to remove acquisition wrapping
      ui_domain = Acquisition.aq_base(ui_domain)
86 87
      sm.registerUtility(ui_domain, ITranslationDomain, 'ui')
      del portal._save_ui_domain
88

89 90 91 92 93 94 95
HTTP_OK = 200
HTTP_UNAUTHORIZED = 401
HTTP_REDIRECT = 302

class TestERP5Core(ERP5TypeTestCase, ZopeTestCase.Functional):
  """Test for erp5_core business template.
  """
96 97
  run_all_test = 1
  quiet = 1
98

99
  manager_username = 'rc'
100
  manager_password = 'w'
101 102 103 104

  def getTitle(self):
    return "ERP5Core"

105 106 107 108 109
  def getBusinessTemplateList(self):
    """
    """
    return ('erp5_base', )

110 111
  def login(self, quiet=0, run=run_all_test):
    uf = self.getPortal().acl_users
112 113
    uf._doAddUser(self.manager_username, self.manager_password, ['Manager'], [])
    user = uf.getUserById(self.manager_username).__of__(uf)
114 115
    newSecurityManager(None, user)

116
  def afterSetUp(self):
117
    self.login()
118 119
    self.portal_id = self.portal.getId()
    self.auth = '%s:%s' % (self.manager_username, self.manager_password)
120

Jérome Perrin's avatar
Jérome Perrin committed
121
  def beforeTearDown(self):
122
    self.abort()
123
    unregister_translation_domain_fallback()
Jérome Perrin's avatar
Jérome Perrin committed
124 125 126 127 128
    if 'test_folder' in self.portal.objectIds():
      self.portal.manage_delObjects(['test_folder'])
    self.portal.portal_selections.setSelectionFor('test_selection', None)
    self.tic()

129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
  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'
149 150


151 152
    skins_tool = self.portal.portal_skins
    types_tool = self.portal.portal_types
153
    self.failIf(self.portal._getOb(module_id, None) is not None)
154 155 156 157
    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)

158 159 160 161 162 163
    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)
164 165 166
    self.failUnless(self.portal._getOb(module_id, None) is not None)
    self.assertEquals(module_title,
                      self.portal._getOb(module_id).getTitle())
167 168
    self.assertNotEqual(types_tool.getTypeInfo(module_portal_type), None)
    self.assertNotEqual(types_tool.getTypeInfo(object_portal_type), None)
169 170
    self.assertTrue(module_portal_type
                      in self.portal.getPortalModuleTypeList())
171

172 173 174 175 176 177
    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)
178

179 180 181 182 183 184 185 186 187 188
    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()
189
    self.portal.unittest_module.newContent(id='document', portal_type=object_portal_type)
190 191
    self.portal.unittest_module.document.view()

192 193 194 195 196
    # 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())
197 198

    type_information = self.portal.portal_types[module_portal_type]
199 200 201
    self.assertTrue('business_application'
                    in type_information.getTypeBaseCategoryList())

202 203 204 205 206 207 208 209
  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" %
210
           (pprint.pformat(expected), pprint.pformat(got)))
211 212
    self.assertEquals(expected, got, msg)

213
  def test_manager_actions_on_portal(self):
214 215 216 217 218 219 220 221 222 223
    # 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'},
224 225
                           {'id': 'property_sheet_tool',
                            'title': 'Configure Property Sheets'},
226 227
                           {'id': 'portal_alarms_action', 
                            'title': 'Configure Alarms'},
228 229
                           {'title': 'Undo', 'id': 'undo'}],
                'object': [],
230 231
                'object_action': [{'id': 'post_query', 'title': 'Post a Query'}],
                'object_jump': [{'id': 'jump_query', 'title': 'Queries'}],
232 233 234 235 236 237 238 239 240 241
                '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)

242 243 244 245 246
  def test_member_actions_on_portal(self):
    # as Member
    self.createUser('usual_member')
    self.logout()
    self.tic()
247
    super(TestERP5Core, self).login('usual_member')
248 249 250 251 252 253 254 255 256 257 258 259 260
    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):
261 262 263 264 265 266 267 268 269 270 271 272
    # 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)
273

274 275 276
  def test_frontpage(self):
    """Test we can view the front page.
    """
277
    response = self.publish(self.portal_id, self.auth)
278
    self.assertEquals(HTTP_OK, response.getStatus())
279

280 281 282
  def test_login_form(self):
    """Test anonymous user are redirected to login_form
    """
283
    response = self.publish(self.portal_id)
284 285 286 287
    self.assertEquals(HTTP_REDIRECT, response.getStatus())
    self.assertEquals('%s/login_form' % self.portal.absolute_url(),
                      response.getHeader('Location'))

288 289 290 291 292 293
  def test_view_tools(self):
    """Test we can view tools."""
    for tool in ('portal_categories',
                 'portal_templates',
                 'portal_rules',
                 'portal_alarms',):
294
      response = self.publish('%s/%s' % (self.portal_id, tool), self.auth)
295
      self.assertEquals(HTTP_OK, response.getStatus(),
296 297
                        "%s: %s (%s)" % (tool, response.getStatus(),
                                         str(response)))
298

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

  def test_jump_action_translated(self):
    """Tests jump actions are translated"""
    translation_service = DummyTranslationService()
    setGlobalTranslationService(translation_service)
317
    # adds a new jump action to Template Tool portal type
Julien Muchembled's avatar
Julien Muchembled committed
318 319 320 321 322 323
    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')
324
    response = self.publish('%s/portal_templates' %
325
                            self.portal_id, self.auth)
326 327 328 329
    self.assertEquals(HTTP_OK, response.getStatus())
    self.failUnless(('Dummy Jump Action', {}) in
                      translation_service._translated['ui'])

330 331
  def test_error_log(self):
    self.failUnless('error_log' in self.portal.objectIds())
332 333 334 335
    self.failUnless(self.portal.error_log.getProperties()['copy_to_zlog'])
    self.failIf('Unauthorized' in
                self.portal.error_log.getProperties()['ignored_exceptions'])

336 337 338 339
  def test_03_getDefaultModule(self, quiet=quiet, run=run_all_test):
    """
    test getDefaultModule method
    """
340
    if not run:
341 342 343 344 345 346 347 348 349
      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
350

351 352 353 354 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
    # 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())

380 381 382 383
  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
384 385 386 387
    login_name = 'very_very_looooooooooooooooooooooooooooooooooooooooooooooooooooo' + \
    'oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong_login_name'
    password = 'very_long_passworddddddddddddddddddddddddddddddddddddddddddddddddd' + \
    'ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'
388 389 390 391 392
    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)
393
    self.auth = '%s:%s' % (login_name, password)
394
    self.commit()
395 396 397 398 399 400 401 402 403 404

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

    self.tic()

    self.assertEqual(
      len(portal.portal_catalog(portal_type='Preference',
                                title='My Test Preference')),
      1)
405
    response = self.publish(self.portal_id, self.auth)
406
    self.assertEquals(HTTP_OK, response.getStatus())
407

408 409 410 411 412 413 414 415 416
  def _Folder_delete(self, *object_list):
    selection_name = 'test_selection'
    portal_selections = self.portal.portal_selections
    portal_selections.setSelectionCheckedUidsFor(selection_name,
      [x.getUid() for x in object_list])
    md5_string = portal_selections.getSelectionChecksum(selection_name)
    return object_list[0].getParentValue().Folder_delete(
      selection_name=selection_name, md5_object_uid_list=md5_string)

Jérome Perrin's avatar
Jérome Perrin committed
417 418 419 420 421
  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')
    self.tic()
422
    redirect = self._Folder_delete(document_1, document_2)
Jérome Perrin's avatar
Jérome Perrin committed
423
    self.assert_('Deleted.' in redirect, redirect)
424
    self.assertEqual(module.objectCount(), 0)
Jérome Perrin's avatar
Jérome Perrin committed
425 426 427

  def test_Folder_delete_related_object(self):
    # deletion is refused if there are related objects
428 429 430 431 432 433 434
    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'])
435
    self.commit()
436
    self.assertEqual(0, organisation.getRelationCountForDeletion())
Jérome Perrin's avatar
Jérome Perrin committed
437
    self.tic()
438 439 440
    self.assertEqual(2, organisation.getRelationCountForDeletion())
    self.assertEqual(0, person.getRelationCountForDeletion())
    def delete(assert_deleted, obj):
441
      redirect = self._Folder_delete(obj)
442 443 444 445 446 447 448 449 450 451 452
      self.assertTrue(('Sorry, 1 item is in use.', 'Deleted.')[assert_deleted]
                      in redirect, redirect)
      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
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469

  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())
    self.tic()
    self.assertEquals([document_1],
        self.portal.portal_categories.getRelatedValueList(document_2))

    document_1.manage_permission('View', [], acquire=0)
    document_1.manage_permission('Access contents information', [], acquire=0)
470
    redirect = self._Folder_delete(document_2)
Jérome Perrin's avatar
Jérome Perrin committed
471
    self.assert_('Sorry, 1 item is in use.' in redirect, redirect)
472
    self.assertEqual(module.objectCount(), 2)
Jérome Perrin's avatar
Jérome Perrin committed
473

474
  def test_getPropertyForUid(self):
475 476 477 478 479
    self.assertEqual([], [
      (i.getId(), i.getUid(), i.getProperty('uid'))
      for i in self.portal.objectValues()
      if i.getId() not in ('portal_uidhandler',) and
         0 != i.getUid() != i.getProperty('uid')])
Jérome Perrin's avatar
Jérome Perrin committed
480

481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
  def test_site_manager_and_translation_migration(self):
    from zope.site.hooks import getSite, setSite
    from zope.component import queryUtility
    from zope.i18n.interfaces import ITranslationDomain
    # check translation is working normaly
    erp5_ui_catalog = self.portal.Localizer.erp5_ui
    self.assertEqual(queryUtility(ITranslationDomain, 'erp5_ui'),
                     erp5_ui_catalog)
    self.assertEqual(queryUtility(ITranslationDomain, 'ui'), erp5_ui_catalog)
    # let's damage it intentionally to see how it is rebuild from scratch
    # in a migration from Zope 2.8
    sm = self.portal.getSiteManager()
    sm.unregisterUtility(provided=ITranslationDomain, name=u'ui')
    self.assertEqual(queryUtility(ITranslationDomain, 'erp5_ui'),
                     erp5_ui_catalog)
    self.assertEqual(queryUtility(ITranslationDomain, 'ui'), None)
    # now let's simulate a site just migrated from Zope 2.8 that's being
    # accessed for the first time:
    old_site = getSite()
    try:
      setSite()
      # Sites from Zope2.8 don't have a site_manager yet.
      del self.portal._components
      # check that we can't get any translation utility
      self.assertEqual(queryUtility(ITranslationDomain, 'erp5_ui'), None)
      # Now simulate first access. Default behaviour from
      # ObjectManager is to raise a ComponentLookupError here:
      setSite(self.portal)
      # This should have automatically reconstructed the i18n utility
      # registrations:
      self.assertEqual(queryUtility(ITranslationDomain, 'erp5_ui'),
                       erp5_ui_catalog)
      self.assertEqual(queryUtility(ITranslationDomain, 'ui'), erp5_ui_catalog)
    finally:
      # clean everything up, we don't want to mess the test environment
516
      self.abort()
517 518
      setSite(old_site)

519 520 521 522
def test_suite():
  suite = unittest.TestSuite()
  suite.addTest(unittest.makeSuite(TestERP5Core))
  return suite
523