Commit a81d5b6c authored by iv's avatar iv Committed by iv

ERP5Workflow: test refactoring

- erp5_test_workflow
  *  add testWorklowMixin test case for inheritance purpose,
  *  move testWorklist ERP5 test to portal_component
parent fde6505a
......@@ -56,6 +56,7 @@
<tuple>
<string>title</string>
<string>description</string>
<string>comment</string>
<string>reference</string>
<string>short_title</string>
</tuple>
......
import unittest
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from erp5.component.test.testWorkflowMixin import testWorkflowMixin
from Products.DCWorkflow.DCWorkflow import ValidationFailed
from DateTime import DateTime
class TestERP5WorkflowMixin(ERP5TypeTestCase):
class TestERP5WorkflowMixin(testWorkflowMixin, object):
initial_dc_workflow_id = "testing_initial_dc_workflow"
initial_dc_interaction_workflow_id = "testing_initial_dc_interaction_workflow"
......@@ -43,21 +43,6 @@ class TestERP5WorkflowMixin(ERP5TypeTestCase):
def doActionFor(self, document, action):
self.portal.portal_workflow.doActionFor(document, action, wf_id=self.workflow_id)
def getWorklistDocumentCountFromActionName(self, action_name):
self.assertEqual(action_name[-1], ')')
left_parenthesis_offset = action_name.rfind('(')
self.assertNotEquals(left_parenthesis_offset, -1)
return int(action_name[left_parenthesis_offset + 1:-1])
def checkWorklist(self, action_list, name, count):
entry_list = [x for x in action_list if x['name'].startswith(name)
and 'workflow_id' in x
and x['workflow_id'] == self.workflow_id]
self.assertEqual(len(entry_list), count and 1)
if count:
self.assertEqual(count,
self.getWorklistDocumentCountFromActionName(entry_list[0]['name']))
def clearCache(self):
self.portal.portal_caches.clearAllCache()
......@@ -186,7 +171,7 @@ class TestERP5WorkflowMixin(ERP5TypeTestCase):
self.tic() # reindexing for security
self.clearCache()
result = workflow_tool.listActions(object=new_object)
self.checkWorklist(result, 'Document', 1)
self.checkWorklist(result, 'Document', 1, workflow_id=self.workflow_id)
def test_11_testValidationInteraction(self):
"""
......
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
class testWorkflowMixin(ERP5TypeTestCase):
def getWorklistDocumentCountFromActionName(self, action_name):
self.assertEqual(action_name[-1], ')')
left_parenthesis_offset = action_name.rfind('(')
self.assertNotEquals(left_parenthesis_offset, -1)
return int(action_name[left_parenthesis_offset + 1:-1])
def checkWorklist(self, action_list, name, count, url_parameter_dict=None, workflow_id=None):
entry_list = [
x for x in action_list if x['name'].startswith(name)
and (
workflow_id is None
or 'workflow_id' in x
and x['workflow_id'] == workflow_id
)
]
self.assertEqual(len(entry_list), count and 1)
if count:
self.assertEqual(count,
self.getWorklistDocumentCountFromActionName(entry_list[0]['name']))
if entry_list and url_parameter_dict:
url = entry_list[0].get('url')
self.assertTrue(url, 'Can not check url parameters without url')
url = '%s%s' % (self.portal.getId(), url[len(self.portal.absolute_url()):])
# Touch URL to save worklist parameters in listbox selection
self.publish(url, 'manager:') # XXX: troubles running live test, returns HTTP error 500
self.commit()
selection_parameter_dict = self.portal.portal_selections.getSelectionParamsFor(
self.module_selection_name)
for parameter, value in url_parameter_dict.iteritems():
self.assertIn(parameter, selection_parameter_dict)
self.assertEqual(value, selection_parameter_dict[parameter])
def clearCache(self):
self.portal.portal_caches.clearAllCache()
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Test Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>testWorkflowMixin</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testWorkflowMixin</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Test Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
</pickle>
<pickle>
<tuple>
<none/>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</tuple>
</pickle>
</record>
</ZopeData>
##############################################################################
#
# Copyright (c) 2007 Nexedi SA 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.
#
##############################################################################
import re
import unittest
from zLOG import LOG, INFO, WARNING
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Testing.ZopeTestCase.PortalTestCase import PortalTestCase
from erp5.component.test.testWorkflowMixin import testWorkflowMixin
class TestWorklist(ERP5TypeTestCase):
class TestWorklist(testWorkflowMixin):
run_all_test = 1
quiet = 1
login = PortalTestCase.login
checked_portal_type = 'Organisation'
module_selection_name = 'organisation_module_selection'
......@@ -59,53 +27,62 @@ class TestWorklist(ERP5TypeTestCase):
int_catalogued_variable_id = 'int_index'
int_value = 1
user_dict = {
'foo': [None, None],
'bar': [None, None],
}
def getTitle(self):
return "Worklist"
def clearModule(self, module):
module.manage_delObjects(list(module.objectIds()))
def cleanUp(self):
# delete all objects of some modules
for module in [self.portal.organisation_module, self.portal.portal_categories.region,
self.portal.portal_categories.role]:
module.manage_delObjects(list(module.objectIds()))
# delete "person" objects
module = self.portal.person_module
for person in self.user_dict.keys():
if person in list(module.objectIds()):
module.manage_delObjects([person])
self.tic()
def beforeTearDown(self):
self.clearModule(self.portal.person_module)
self.clearModule(self.portal.organisation_module)
self.clearModule(self.portal.portal_categories.region)
self.clearModule(self.portal.portal_categories.role)
def getBusinessTemplateList(self):
"""
Return list of bt5 to install
"""
return ('erp5_base',)
def afterSetUp(self):
self.cleanUp()
self.logMessage("Create users")
self.createERP5Users()
self.clearCache()
self.tic()
def getUserFolder(self):
"""
Return the user folder
"""
return getattr(self.getPortal(), 'acl_users', None)
def beforeTearDown(self):
self.cleanUp()
def createManagerAndLogin(self):
"""
Create a simple user in user_folder with manager rights.
This user will be used to initialize data in the method afterSetup
"""
self.getUserFolder()._doAddUser('manager', '', ['Manager'], [])
acl_user_folder = getattr(self.getPortal(), 'acl_users', None)
acl_user_folder._doAddUser('manager', '', ['Manager'], [])
self.login('manager')
def createERP5Users(self, user_dict):
def createERP5Users(self):
"""
Create all ERP5 users needed for the test.
ERP5 user = Person object + Assignment object in erp5 person_module.
"""
portal = self.getPortal()
module = portal.getDefaultModule("Person")
# Create the Person.
for user_login, user_data in user_dict.items():
for user_login, user_data in self.user_dict.items():
# Create the Person.
self.logMessage("Create user: %s" % user_login)
person = module.newContent(
portal_type='Person',
reference=user_login,
id=user_login,
password='hackme',
)
# Create the Assignment.
......@@ -120,22 +97,6 @@ class TestWorklist(ERP5TypeTestCase):
# Reindexing is required for the security to work
self.tic()
def createUsers(self):
"""
Create all users needed for the test
"""
self.createERP5Users(self.getUserDict())
def getUserDict(self):
"""
Return dict of users needed for the test
"""
user_dict = {
'foo': [None, None],
'bar': [None, None],
}
return user_dict
def createDocument(self, **kw):
module = self.getPortal().getDefaultModule(self.checked_portal_type)
result = module.newContent(portal_type=self.checked_portal_type, **kw)
......@@ -143,93 +104,76 @@ class TestWorklist(ERP5TypeTestCase):
assert result.getValidationState() == self.checked_validation_state
return result
def getWorklistDocumentCountFromActionName(self, action_name):
self.assertEqual(action_name[-1], ')')
left_parenthesis_offset = action_name.rfind('(')
self.assertNotEquals(left_parenthesis_offset, -1)
return int(action_name[left_parenthesis_offset + 1:-1])
def associatePropertySheet(self):
self._addPropertySheet(self.checked_portal_type, 'SortIndex')
def addWorkflowCataloguedVariable(self, workflow_id, variable_id):
# add new workflow compatibility
# Add new workflow compatibility
# Will otherwise add dynamic variable in worklist.
workflow_value = self.getWorkflowTool()[workflow_id]
if workflow_value.__class__.__name__ == 'Workflow':
# Will add dynamic variable in worklist.
pass
else:
if workflow_value.__class__.__name__ != 'Workflow':
variables = workflow_value.variables
variables.addVariable(variable_id)
if not getattr(variables, variable_id, None):
variables.addVariable(variable_id)
variable_value = variables[variable_id]
assert variable_value.for_catalog == 1
def createWorklist(self, workflow_id, worklist_id, actbox_name,
actbox_url=None, **kw):
# add new workflow compatibility
tales_re = re.compile(r'(\w+:)?(.*)')
workflow_value = self.getWorkflowTool()[workflow_id]
actbox_name='%s (%%(count)s)' % actbox_name
# add new workflow compatibility
if workflow_value.__class__.__name__ == 'Workflow':
if getattr(workflow_value, worklist_id, None):
workflow_value.manage_delObjects([worklist_id])
worklist_value = workflow_value.newContent(portal_type='Worklist')
worklist_value.setReference(worklist_id)
# Configure new workflow:
actbox_name='%s (%%(count)s)' % actbox_name
worklist_value.setActboxName(str(actbox_name))
worklist_value.setActboxUrl(str(actbox_url))
worklist_value.setActboxCategory(str('global'))
props={k if k.startswith('guard_') else 'variable_' + k: v
for k, v in kw.iteritems()}
if 'variable_portal_type' in props:
v = props.get('variable_portal_type', None)
if v:
worklist_value.setMatchedPortalTypeList(v)
if 'variable_validation_state' in props:
v = props.get('variable_validation_state', None)
if v:
worklist_value.setMatchedValidationState('state_'+v)
if 'variable_' + self.int_catalogued_variable_id in props:
variable_ref = self.int_catalogued_variable_id
v = props.get('variable_'+self.int_catalogued_variable_id, None)
if v:
# Add a local worklist variable:
variable_value = worklist_value._getOb('variable_' + self.int_catalogued_variable_id, None)
if variable_value is None:
variable_value = worklist_value.newContent(portal_type='Worklist Variable')
variable_value.setReference(variable_ref)
variable_value.setInitialValue(str(v))
# test04 related key
if 'variable_region_uid' in props:
v = props.get('variable_region_uid', None)
if v:
variable_value = worklist_value._getOb('variable_region_uid', None)
if variable_value is None:
variable_value = worklist_value.newContent(portal_type='Worklist Variable')
variable_value.setReference('region_uid')
variable_value.setDefaultExpr(v)
if 'variable_base_category_id' in props:
variable_value = worklist_value._getOb('variable_base_category_id', None)
v = props.get('variable_base_category_id', None)
if variable_value is None:
variable_value = worklist_value.newContent(portal_type='Worklist Variable')
variable_value.setReference('base_category_id')
variable_value.setInitialValue(v)
# Update guard configuration for view and guard value.
if 'guard_roles' in props:
v = props.get('guard_roles', '')
if v:
worklist_value.setRoleList([ var.strip() for var in v.split(';') ])
if 'guard_expr' in props:
v = props.get('guard_expr', '')
if v:
worklist_value.setExpression(v)
objectAndMethodToApplyArgumentTo = [
(worklist_value, 'setActboxName', actbox_name),
(worklist_value, 'setActboxUrl', str(actbox_url)),
(worklist_value, 'setActboxCategory', 'global'),
(worklist_value, 'setMatchedPortalTypeList', kw.get('portal_type')),
(worklist_value, 'setMatchedValidationState', '' if not kw.get('validation_state') \
else 'state_' + kw.get('validation_state'))
]
for (kw_key, method) in [(self.int_catalogued_variable_id, 'setInitialValue'),
('region_uid', 'setDefaultExpr'),
('base_category_id', 'setInitialValue')]:
var_value = kw.get(kw_key, None)
var_object = None
# Create variable for worklist if not exists:
if var_value is not None:
var_object = worklist_value._getOb(kw_key, None)
if var_object is None:
var_object = worklist_value.newContent(portal_type='Worklist Variable')
var_object.setReference(kw_key)
objectAndMethodToApplyArgumentTo.append(var_object, method, var_value)
guard_roles_string = kw.get('guard_roles', '')
guard_role_list = [ var.strip() for var in guard_roles_string.split(';') ]
objectAndMethodToApplyArgumentTo += [
(worklist_value, 'setRoleList', guard_role_list),
(worklist_value, 'setExpression', kw.get('guard_expr'))
]
# Configure new worklist:
for (obj, method_name, argument) in objectAndMethodToApplyArgumentTo:
method = (getattr(obj, method_name, None))
if method is not None and argument not in [None, '']:
method(argument)
worklist_value.getGuard()
else:
worklists = workflow_value.worklists
if worklists._getOb(worklist_id, None):
worklists.deleteWorklists([worklist_id])
worklists.addWorklist(worklist_id)
worklist_value = worklists._getOb(worklist_id)
worklist_value.setProperties('',
actbox_name='%s (%%(count)s)' % actbox_name, actbox_url=actbox_url,
actbox_name=actbox_name, actbox_url=actbox_url,
props={k if k.startswith('guard_') else 'var_match_' + k: v
for k, v in kw.iteritems()})
......@@ -275,28 +219,16 @@ class TestWorklist(ERP5TypeTestCase):
self.worklist_int_variable_id,
])
def clearCache(self):
self.portal.portal_caches.clearAllCache()
def createCategories(self):
category_tool = self.getCategoryTool()
for base_category, category_list in (
('region', ('somewhere', 'elsewhere')),
('role', ('client', 'supplier'))):
newContent = category_tool[base_category].newContent
for category in category_list:
if not getattr(category_tool[base_category], category, None):
newContent(portal_type='Category', id=category)
def checkWorklist(self, result, name, count, url_parameter_dict=None):
entry_list = [x for x in result if x['name'].startswith(name)]
self.assertEqual(len(entry_list), count and 1)
if count:
self.assertEqual(count,
self.getWorklistDocumentCountFromActionName(entry_list[0]['name']))
if not entry_list:
return
url = entry_list[0].get('url')
if url_parameter_dict:
self.assertTrue(url, 'Can not check url parameters without url')
url = '%s%s' % (self.portal.getId(), url[len(self.portal.absolute_url()):])
# Touch URL to save worklist parameters in listbox selection
self.publish(url, 'manager:') # XXX which user ?
selection_parameter_dict = self.portal.portal_selections.getSelectionParamsFor(
self.module_selection_name)
for parameter, value in url_parameter_dict.iteritems():
self.assertTrue(parameter in selection_parameter_dict)
self.assertEqual(value, selection_parameter_dict[parameter])
def test_01_permission(self, quiet=0, run=run_all_test):
"""
......@@ -307,9 +239,8 @@ class TestWorklist(ERP5TypeTestCase):
workflow_tool = self.portal.portal_workflow
self.logMessage("Create users")
self.createManagerAndLogin()
self.createUsers()
self.logMessage("Create worklists")
self.associatePropertySheet()
self.addWorkflowCataloguedVariable(self.checked_workflow,
......@@ -444,14 +375,10 @@ class TestWorklist(ERP5TypeTestCase):
self.createManagerAndLogin()
self.logMessage("Create categories")
for base_category, category_list in (
('region', ('somewhere', 'elsewhere')),
('role', ('client', 'supplier'))):
newContent = self.getCategoryTool()[base_category].newContent
for category in category_list:
newContent(portal_type='Category', id=category)
self.createCategories()
self.logMessage("Create worklists using 'base_category_id' related key")
self.addWorkflowCataloguedVariable(self.checked_workflow,
'base_category_id')
self.createWorklist(self.checked_workflow, 'region_worklist', 'has_region',
......@@ -495,7 +422,6 @@ class TestWorklist(ERP5TypeTestCase):
workflow_tool = self.getWorkflowTool()
self.createManagerAndLogin()
self.createUsers()
self.logMessage("Create worklists with guard expression")
self.createWorklist(self.checked_workflow, 'guard_expression_worklist',
......@@ -540,12 +466,7 @@ class TestWorklist(ERP5TypeTestCase):
self.createManagerAndLogin()
self.logMessage("Create categories")
for base_category, category_list in (
('region', ('somewhere', 'elsewhere')),
('role', ('client', 'supplier'))):
newContent = self.getCategoryTool()[base_category].newContent
for category in category_list:
newContent(portal_type='Category', id=category)
self.createCategories()
self.logMessage("Create worklists using 'region_uid' related key"\
" and TALES Expression")
......@@ -566,8 +487,8 @@ class TestWorklist(ERP5TypeTestCase):
self.tic()
self.clearCache()
self.logMessage(" Check no document has region categories defined")
result = workflow_tool.listActions(object=document)
self.checkWorklist(result, 'has_semewhere_region', 0)
list_action = workflow_tool.listActions(object=document)
self.checkWorklist(list_action, 'has_semewhere_region', 0)
self.logMessage(" Creates documents with region categories defined")
......@@ -577,13 +498,12 @@ class TestWorklist(ERP5TypeTestCase):
self.tic()
self.clearCache()
self.logMessage(
" Check there are documents with region categories defined")
result = workflow_tool.listActions(object=document)
self.logMessage(" Check there are documents with region categories defined")
action_list = workflow_tool.listActions(object=document)
url_parameter_dict = {'region_uid': [str(self.portal.portal_categories.\
getCategoryUid("region/somewhere"))],
'portal_type': [self.checked_portal_type]}
self.checkWorklist(result, 'has_semewhere_region', 2,
self.checkWorklist(action_list, 'has_semewhere_region', 2,
url_parameter_dict=url_parameter_dict)
finally:
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Test Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>testWorklist</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testWorklist</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Test Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
</pickle>
<pickle>
<tuple>
<none/>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</tuple>
</pickle>
</record>
</ZopeData>
This template offer simulation data for erp5_workflow.
\ No newline at end of file
This template offer simulation data for erp5_workflow (old DC workflow, ERP5 workflow, worklist).
\ No newline at end of file
test.erp5.testWorkflowAndDCWorkflow
\ No newline at end of file
test.erp5.testWorkflowAndDCWorkflow
test.erp5.testWorkflowMixin
test.erp5.testWorklist
\ No newline at end of file
......@@ -68,6 +68,7 @@ class ERP5TypeLiveTestCase(ERP5TypeTestCaseMixin):
defined.
"""
portal = None
_added_property_sheets = {}
def getPortalName(self):
""" Return the default ERP5 site id.
......
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