Commit 56e16adc authored by Jérome Perrin's avatar Jérome Perrin

testXHTML: test portal types also from tools

Some portal types such as Business Template were not tested by testXHTML
because this tests started by modules and recursively tests views based
on allowed content types. Because of this approach, types that are not
created in a module but in a tool were never tested.

With unittest, the only way to dynamically add test methods to a class
is to generate a test class in test_suite function. At this stage, the
ERP5 site is not created yet, so the test had to introspects business
templates XML.

This now uses a slightly different approach, instead of finding modules
and chain of allowed content types from business template XML, we only
use business template to introspect the list of actions.
The lookup of the appropriate containers is no longer done before setup
by static analysis of business templates XML, but later once the site is
created, by dynamic analysis of the modules and allowed content types on
the running ERP5 site during the test method.
If we don't find a chain of portal types, we create the test document in
portal_trash, a tool without filter of content types.

This way, we can test all views of all portal types. This revealed a few
problems:
 - we need developer role to create components in portal_components, for
this we add developer role to the current user.
 - Delivery Cell portal type looks not used, there are no container
accepting it. We don't test delivery cell views for this reason.
 - VCS view on business template needs preferences and working copy
setup. We just mark this test as expected failure for now.
 - Solver Decision has a form conditionnaly displayed when there's a
relation to a solver, but this test does not evaluate action conditions
and does not allow to call a script (that would made it possible to
modify the document so that the condition is true). For now we also
mark this as expected failure.
parent 439745de
Pipeline #6856 failed with stage
in 0 seconds
...@@ -36,6 +36,7 @@ from requests.packages.urllib3.util.retry import Retry ...@@ -36,6 +36,7 @@ from requests.packages.urllib3.util.retry import Retry
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from Testing import ZopeTestCase from Testing import ZopeTestCase
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.ERP5Type.tests.utils import addUserToDeveloperRole
from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import getToolByName
from zLOG import LOG from zLOG import LOG
# You can invoke same tests in your favourite collection of business templates # You can invoke same tests in your favourite collection of business templates
...@@ -507,6 +508,7 @@ class TestXHTML(TestXHTMLMixin): ...@@ -507,6 +508,7 @@ class TestXHTML(TestXHTMLMixin):
uf._doAddUser('seb', '', ['Manager'], []) uf._doAddUser('seb', '', ['Manager'], [])
self.loginByUserName('seb') self.loginByUserName('seb')
addUserToDeveloperRole('seb') # required to create content in portal_components
self.enableDefaultSitePreference() self.enableDefaultSitePreference()
def enableDefaultSitePreference(self): def enableDefaultSitePreference(self):
...@@ -653,7 +655,7 @@ def validate_xhtml(validator, source, view_name, bt_name): ...@@ -653,7 +655,7 @@ def validate_xhtml(validator, source, view_name, bt_name):
return len(message) == 1, '\n'.join(message) return len(message) == 1, '\n'.join(message)
def makeTestMethod(validator, module_id, portal_type, view_name, bt_name): def makeTestMethod(validator, portal_type, view_name, bt_name):
def createSubContent(content, portal_type_list): def createSubContent(content, portal_type_list):
if not portal_type_list: if not portal_type_list:
...@@ -664,102 +666,76 @@ def makeTestMethod(validator, module_id, portal_type, view_name, bt_name): ...@@ -664,102 +666,76 @@ def makeTestMethod(validator, module_id, portal_type, view_name, bt_name):
content.newContent(portal_type=portal_type_list[0]), content.newContent(portal_type=portal_type_list[0]),
portal_type_list[1:]) portal_type_list[1:])
def testMethod(self):
module = getattr(self.portal, module_id)
portal_type_list = portal_type.split('/')
object = createSubContent(module, portal_type_list) def findContentChain(portal, target_portal_type):
view = getattr(object, view_name) # type: (erp5.portal_type.ERP5Site,str) -> Tuple[erp5.portal_type.Folder, Tuple[str, ...]]
"""Returns the module and the chain of portal types to create a document of target_portal_type.
This tries all allowed content types up to three levels and if not found, use portal_trash,
which allows anything.
"""
# These types have a special `newContent` which does not really follow the interface, we
# cannot not use them as container.
invalid_container_type_set = {
'Session Tool',
'Contribution Tool',
}
# first look modules and their content to find a real container chain.
for module in portal.contentValues():
module_type = module.getTypeInfo()
if module_type is not None:
if module_type.getId() == target_portal_type:
return module, ()
if module_type.isTypeFilterContentType() \
and module_type.getId() not in invalid_container_type_set:
for allowed_type in module.allowedContentTypes():
# Actions on portal_actions are global actions which can be rendered on any context.
# We don't test them on all portal types, only on the first type "top level document"
if target_portal_type in ('portal_actions', allowed_type.getId()):
return module, (allowed_type.getId(),)
for sub_allowed_type in allowed_type.getTypeAllowedContentTypeList():
if target_portal_type == sub_allowed_type:
return module, (allowed_type.getId(), target_portal_type)
if sub_allowed_type in portal.portal_types:
for sub_sub_allowed_type in portal.portal_types[
sub_allowed_type].getTypeAllowedContentTypeList():
if target_portal_type == sub_sub_allowed_type:
return module, (
allowed_type.getId(),
sub_allowed_type,
target_portal_type,
)
# we did not find a valid chain of containers, so we'll fallback to creating
# in portal_trash, which allow anything.
# We still make one attempt at finding a valid container.
for ti in portal.portal_types.contentValues():
if ti.getId() not in invalid_container_type_set\
and target_portal_type in ti.getTypeAllowedContentTypeList():
return portal.portal_trash, (ti.getId(), target_portal_type,)
# no suitable container found, use directly portal_trash.
ZopeTestCase._print(
'Could not find container for %s. Using portal_trash as a container\n'
% target_portal_type)
return portal.portal_trash, (target_portal_type,)
def testMethod(self):
module, portal_type_list = findContentChain(
self.portal,
portal_type)
document = createSubContent(module, portal_type_list)
view = getattr(document, view_name)
self.assert_(*validate_xhtml( validator=validator, self.assert_(*validate_xhtml( validator=validator,
source=view(), source=view(),
view_name=view_name, view_name=view_name,
bt_name=bt_name)) bt_name=bt_name))
return testMethod return testMethod
def testPortalTypeViewRecursivly(test_class, validator, module_id,
business_template_info, business_template_info_list, portal_type_list, def addTestMethodDynamically(
portal_type_path_dict, base_path, tested_portal_type_list): test_class,
''' validator,
This function go on all portal_type recursivly if the portal_type could target_business_templates,
contain other portal_types and make a test for all view that have action expected_failure_list=()):
'''
# iteration over all allowed portal_types inside the module/portal_type
for portal_type in portal_type_list:
portal_path = portal_type_path_dict[portal_type]
if portal_type not in tested_portal_type_list:
# this portal type haven't been tested yet
backuped_module_id = module_id
backuped_business_template_info = business_template_info
if not business_template_info.actions.has_key(portal_type):
# search in other bt :
business_template_info = None
for bt_info in business_template_info_list:
if bt_info.actions.has_key(portal_type):
business_template_info = bt_info
break
if not business_template_info:
LOG("Can't find the action :", 0, portal_type)
break
# create the object in portal_trash module
module_id = 'portal_trash'
for business_template_info in business_template_info_list:
if portal_type not in business_template_info.actions:
continue
for action_information in business_template_info.actions[portal_type]:
if (action_information['category'] in ('object_view', 'object_list') and
action_information['visible']==1 and
action_information['action'].startswith('string:${object_url}/') and
len(action_information['action'].split('/'))==2):
view_name = action_information['action'].split('/')[-1].split('?')[0]
method = makeTestMethod(validator,
module_id,
portal_path,
view_name,
business_template_info.title)
method_name = ('test_%s_%s_%s' %
(business_template_info.title,
str(portal_type).replace(' ','_'), # can be unicode
view_name))
method.__name__ = method_name
setattr(test_class, method_name, method)
module_id = backuped_module_id
# add the portal_type to the tested portal_types. This avoid to test many
# times a Portal Type wich is many bt.
tested_portal_type_list.append(portal_type)
new_portal_type_list = []
for tmp_business_template_info in business_template_info_list:
new_portal_type_list.extend(tmp_business_template_info.allowed_content_types.get(portal_type, ()))
new_portal_type_path_dict = {}
if base_path != '':
next_base_path = '%s/%s' % (base_path, portal_type)
# Module portal_type not to have been added to the path because
# this portal type object already existing
elif 'Module' not in portal_type:
next_base_path = portal_type
else:
next_base_path = ''
for pt in new_portal_type_list:
if next_base_path != '' and 'Module' not in pt:
new_portal_type_path_dict[pt] = '%s/%s' % (next_base_path, pt)
else:
new_portal_type_path_dict[pt] = pt
testPortalTypeViewRecursivly(test_class=test_class,
validator=validator,
module_id=module_id,
business_template_info=backuped_business_template_info,
business_template_info_list=business_template_info_list,
portal_type_list=new_portal_type_list,
portal_type_path_dict=new_portal_type_path_dict,
base_path=next_base_path,
tested_portal_type_list=tested_portal_type_list)
def addTestMethodDynamically(test_class, validator, target_business_templates):
from Products.ERP5.tests.utils import BusinessTemplateInfoTar from Products.ERP5.tests.utils import BusinessTemplateInfoTar
from Products.ERP5.tests.utils import BusinessTemplateInfoDir from Products.ERP5.tests.utils import BusinessTemplateInfoDir
business_template_info_list = [] business_template_info_list = []
...@@ -771,21 +747,27 @@ def addTestMethodDynamically(test_class, validator, target_business_templates): ...@@ -771,21 +747,27 @@ def addTestMethodDynamically(test_class, validator, target_business_templates):
business_template_info = BusinessTemplateInfoTar(url) business_template_info = BusinessTemplateInfoTar(url)
business_template_info_list.append(business_template_info) business_template_info_list.append(business_template_info)
tested_portal_type_list = []
for business_template_info in business_template_info_list: for business_template_info in business_template_info_list:
for module_id, module_portal_type in business_template_info.modules.items(): for portal_type, action_information_list in business_template_info.actions.items():
portal_type_list = [module_portal_type, ] + \ for action_information in action_information_list:
business_template_info.allowed_content_types.get(module_portal_type, []) if (action_information['category'] in ('object_view', 'object_list') and
portal_type_path_dict = dict(zip(portal_type_list, portal_type_list)) action_information['visible']==1 and
testPortalTypeViewRecursivly(test_class=test_class, action_information['action'].startswith('string:${object_url}/') and
validator=validator, len(action_information['action'].split('/'))==2):
module_id=module_id, view_name = action_information['action'].split('/')[-1].split('?')[0]
business_template_info=business_template_info, method = makeTestMethod(
business_template_info_list=business_template_info_list, validator,
portal_type_list=portal_type_list, portal_type,
portal_type_path_dict=portal_type_path_dict, view_name,
base_path = '', business_template_info.title)
tested_portal_type_list=tested_portal_type_list) method_name = ('test_%s_%s_%s' %
(business_template_info.title,
str(portal_type).replace(' ','_'), # can be unicode
view_name))
method.__name__ = method_name
if method_name in expected_failure_list:
method = unittest.expectedFailure(method)
setattr(test_class, method_name, method)
# Two validators are available : nu and tidy # Two validators are available : nu and tidy
...@@ -815,7 +797,20 @@ def test_suite(): ...@@ -815,7 +797,20 @@ def test_suite():
# add erp5_core to the list here to not return it # add erp5_core to the list here to not return it
# on getBusinessTemplateList call # on getBusinessTemplateList call
addTestMethodDynamically(TestXHTML, validator, addTestMethodDynamically(TestXHTML, validator,
('erp5_core',) + TestXHTML.getBusinessTemplateList()) ('erp5_core',) + TestXHTML.getBusinessTemplateList(),
expected_failure_list=(
# this view needs VCS preference set (this test suite does not support
# setting preferences, but this might be a way to fix this).
'test_erp5_forge_Business_Template_BusinessTemplate_viewVcsStatus',
# this view only works when solver decision has a relation to a solver.
# One way to fix this would be to allow a custom "init script" to be called
# on a portal type.
'test_erp5_simulation_Solver_Decision_SolverDecision_viewConfiguration',
# there's no container accepting delivery cell, but delivery cell has
# an interaction workflow which assumes delivery cell is contained in
# a delivery.
'test_erp5_trade_Delivery_Cell_DeliveryCell_view',
))
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestXHTML)) suite.addTest(unittest.makeSuite(TestXHTML))
return suite return suite
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment