class ConfigurableTicketWorkflowTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() config = self.env.config config.set('ticket-workflow', 'change_owner', 'new -> new') config.set('ticket-workflow', 'change_owner.operations', 'set_owner') self.ctlr = TicketSystem(self.env).action_controllers[0] self.ticket_module = TicketModule(self.env) def tearDown(self): self.env.reset_db() def _add_component(self, name='test', owner='owner1'): component = Component(self.env) component.name = name component.owner = owner component.insert() def _reload_workflow(self): self.ctlr.actions = self.ctlr.get_all_actions() def test_get_all_actions_custom_attribute(self): """Custom attribute in ticket-workflow.""" config = self.env.config['ticket-workflow'] config.set('resolve.set_milestone', 'reject') all_actions = self.ctlr.get_all_actions() resolve_action = None for name, attrs in all_actions.items(): if name == 'resolve': resolve_action = attrs self.assertIsNotNone(resolve_action) self.assertIn('set_milestone', list(resolve_action)) self.assertEqual('reject', resolve_action['set_milestone']) def test_owner_from_component(self): """Verify that the owner of a new ticket is set to the owner of the component. """ self._add_component('component3', 'cowner3') req = MockRequest(self.env, method='POST', args={ 'field_reporter': 'reporter1', 'field_summary': 'the summary', 'field_component': 'component3', }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, 1) self.assertEqual('component3', ticket['component']) self.assertEqual('cowner3', ticket['owner']) def test_component_change(self): """New ticket owner is updated when the component is changed. """ self._add_component('component3', 'cowner3') self._add_component('component4', 'cowner4') ticket = insert_ticket(self.env, reporter='reporter1', summary='the summary', component='component3', owner='cowner3', status='new') req = MockRequest(self.env, method='POST', args={ 'id': ticket.id, 'field_component': 'component4', 'submit': True, 'action': 'leave', 'view_time': str(to_utimestamp(ticket['changetime'])), }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, ticket.id) self.assertEqual('component4', ticket['component']) self.assertEqual('cowner4', ticket['owner']) def test_component_change_and_owner_change(self): """New ticket owner is not updated if owner is explicitly changed. """ self._add_component('component3', 'cowner3') self._add_component('component4', 'cowner4') ticket = insert_ticket(self.env, reporter='reporter1', summary='the summary', component='component3', status='new') req = MockRequest(self.env, method='POST', args={ 'id': ticket.id, 'field_component': 'component4', 'submit': True, 'action': 'change_owner', 'action_change_owner_reassign_owner': 'owner1', 'view_time': str(to_utimestamp(ticket['changetime'])), }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, ticket.id) self.assertEqual('component4', ticket['component']) self.assertEqual('owner1', ticket['owner']) def test_old_owner_not_old_component_owner(self): """New ticket owner is not updated if old owner is not the owner of the old component. """ self._add_component('component3', 'cowner3') self._add_component('component4', 'cowner4') ticket = insert_ticket(self.env, reporter='reporter1', summary='the summary', component='component3', owner='owner1', status='new') req = MockRequest(self.env, method='POST', args={ 'id': ticket.id, 'field_component': 'component4', 'submit': True, 'action': 'leave', 'view_time': str(to_utimestamp(ticket['changetime'])), }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, ticket.id) self.assertEqual('component4', ticket['component']) self.assertEqual('owner1', ticket['owner']) def test_new_component_has_no_owner(self): """Ticket is not disowned when the component is changed to a component with no owner. """ self._add_component('component3', 'cowner3') self._add_component('component4', '') ticket = insert_ticket(self.env, reporter='reporter1', summary='the summary', component='component3', owner='cowner3', status='new') req = MockRequest(self.env, method='POST', args={ 'id': ticket.id, 'field_component': 'component4', 'submit': True, 'action': 'leave', 'view_time': str(to_utimestamp(ticket['changetime'])), }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, ticket.id) self.assertEqual('component4', ticket['component']) self.assertEqual('cowner3', ticket['owner']) def _test_get_allowed_owners(self): ticket = insert_ticket(self.env, summary='Ticket 1') self.env.insert_users([('user1', None, None, 1), ('user2', None, None, 1), ('user3', None, None, 1)]) ps = PermissionSystem(self.env) for user in ('user1', 'user3'): ps.grant_permission(user, 'TICKET_MODIFY') self.env.config.set('ticket', 'restrict_owner', True) return ticket def test_get_allowed_owners_returns_set_owner_list(self): """Users specified in `set_owner` for the action are returned.""" req = None action = {'set_owner': ['user4', 'user5']} ticket = self._test_get_allowed_owners() self.assertEqual(['user4', 'user5'], self.ctlr.get_allowed_owners(req, ticket, action)) def test_get_allowed_owners_returns_user_with_ticket_modify(self): """Users with TICKET_MODIFY are are returned if `set_owner` is not specified for the action. """ req = None action = {} ticket = self._test_get_allowed_owners() self.assertEqual(['user1', 'user3'], self.ctlr.get_allowed_owners(req, ticket, action)) def test_transition_to_star(self): """Workflow hint is not be added in a workflow transition to *, for example: <none> -> * AdvancedTicketWorkflow uses the behavior for the triage operation (see #12823) """ config = self.env.config config.set('ticket-workflow', 'create_and_triage', '<none> -> *') config.set('ticket-workflow', 'create_and_triage.operations', 'triage') self._reload_workflow() ticket = Ticket(self.env) req = MockRequest(self.env, path_info='/newticket', method='POST') label, control, hints = \ self.ctlr.render_ticket_action_control(req, ticket, 'create_and_triage') self.assertEqual('create and triage', label) self.assertEqual('', unicode(control)) self.assertEqual('', unicode(hints)) def test_get_actions_by_operation_for_req(self): """Request with no permission checking.""" req = MockRequest(self.env, path_info='/ticket/1') ticket = insert_ticket(self.env, status='new') actions = self.ctlr.get_actions_by_operation_for_req(req, ticket, 'set_owner') self.assertEqual([(0, u'change_owner'), (0, u'reassign')], actions) def test_get_actions_by_operation_for_req_with_ticket_modify(self): """User without TICKET_MODIFY won't have reassign action.""" req = MockRequest(self.env, authname='user1', path_info='/ticket/1') ticket = insert_ticket(self.env, status='new') actions = self.ctlr.get_actions_by_operation_for_req(req, ticket, 'set_owner') self.assertEqual([(0, u'change_owner')], actions) def test_get_actions_by_operation_for_req_without_ticket_modify(self): """User with TICKET_MODIFY will have reassign action.""" PermissionSystem(self.env).grant_permission('user1', 'TICKET_MODIFY') req = MockRequest(self.env, authname='user1', path_info='/ticket/1') ticket = insert_ticket(self.env, status='new') actions = self.ctlr.get_actions_by_operation_for_req(req, ticket, 'set_owner') self.assertEqual([(0, u'change_owner'), (0, u'reassign')], actions) def test_ignores_other_operations(self): """Ignores operations not defined by ConfigurableTicketWorkflow. """ self.env.config.set('ticket-workflow', 'review', 'assigned -> review') self.env.config.set('ticket-workflow', 'review.operations', 'CodeReview') ctw = ConfigurableTicketWorkflow(self.env) ticket = Ticket(self.env) ticket.populate({'summary': '#13013', 'status': 'assigned'}) ticket.insert() req = MockRequest(self.env) self.assertNotIn((0, 'review'), ctw.get_ticket_actions(req, ticket))
class ConfigurableTicketWorkflowTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() config = self.env.config config.set('ticket-workflow', 'change_owner', 'new -> new') config.set('ticket-workflow', 'change_owner.operations', 'set_owner') self.ctlr = TicketSystem(self.env).action_controllers[0] self.ticket_module = TicketModule(self.env) def tearDown(self): self.env.reset_db() def _add_component(self, name='test', owner='owner1'): component = Component(self.env) component.name = name component.owner = owner component.insert() def _reload_workflow(self): self.ctlr.actions = self.ctlr.get_all_actions() def test_get_all_actions_custom_attribute(self): """Custom attribute in ticket-workflow.""" config = self.env.config['ticket-workflow'] config.set('resolve.set_milestone', 'reject') all_actions = self.ctlr.get_all_actions() resolve_action = None for name, attrs in all_actions.items(): if name == 'resolve': resolve_action = attrs self.assertIsNotNone(resolve_action) self.assertIn('set_milestone', list(resolve_action)) self.assertEqual('reject', resolve_action['set_milestone']) def test_owner_from_component(self): """Verify that the owner of a new ticket is set to the owner of the component. """ self._add_component('component3', 'cowner3') req = MockRequest(self.env, method='POST', args={ 'field_reporter': 'reporter1', 'field_summary': 'the summary', 'field_component': 'component3', }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, 1) self.assertEqual('component3', ticket['component']) self.assertEqual('cowner3', ticket['owner']) def test_component_change(self): """New ticket owner is updated when the component is changed. """ self._add_component('component3', 'cowner3') self._add_component('component4', 'cowner4') ticket = insert_ticket(self.env, reporter='reporter1', summary='the summary', component='component3', owner='cowner3', status='new') req = MockRequest(self.env, method='POST', args={ 'id': ticket.id, 'field_component': 'component4', 'submit': True, 'action': 'leave', 'view_time': str(to_utimestamp(ticket['changetime'])), }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, ticket.id) self.assertEqual('component4', ticket['component']) self.assertEqual('cowner4', ticket['owner']) def test_component_change_and_owner_change(self): """New ticket owner is not updated if owner is explicitly changed. """ self._add_component('component3', 'cowner3') self._add_component('component4', 'cowner4') ticket = insert_ticket(self.env, reporter='reporter1', summary='the summary', component='component3', status='new') req = MockRequest(self.env, method='POST', args={ 'id': ticket.id, 'field_component': 'component4', 'submit': True, 'action': 'change_owner', 'action_change_owner_reassign_owner': 'owner1', 'view_time': str(to_utimestamp(ticket['changetime'])), }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, ticket.id) self.assertEqual('component4', ticket['component']) self.assertEqual('owner1', ticket['owner']) def test_old_owner_not_old_component_owner(self): """New ticket owner is not updated if old owner is not the owner of the old component. """ self._add_component('component3', 'cowner3') self._add_component('component4', 'cowner4') ticket = insert_ticket(self.env, reporter='reporter1', summary='the summary', component='component3', owner='owner1', status='new') req = MockRequest(self.env, method='POST', args={ 'id': ticket.id, 'field_component': 'component4', 'submit': True, 'action': 'leave', 'view_time': str(to_utimestamp(ticket['changetime'])), }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, ticket.id) self.assertEqual('component4', ticket['component']) self.assertEqual('owner1', ticket['owner']) def test_new_component_has_no_owner(self): """Ticket is not disowned when the component is changed to a component with no owner. """ self._add_component('component3', 'cowner3') self._add_component('component4', '') ticket = insert_ticket(self.env, reporter='reporter1', summary='the summary', component='component3', owner='cowner3', status='new') req = MockRequest(self.env, method='POST', args={ 'id': ticket.id, 'field_component': 'component4', 'submit': True, 'action': 'leave', 'view_time': str(to_utimestamp(ticket['changetime'])), }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, ticket.id) self.assertEqual('component4', ticket['component']) self.assertEqual('cowner3', ticket['owner']) def _test_get_allowed_owners(self): ticket = insert_ticket(self.env, summary='Ticket 1') self.env.insert_users([('user1', None, None, 1), ('user2', None, None, 1), ('user3', None, None, 1)]) ps = PermissionSystem(self.env) for user in ('user1', 'user3'): ps.grant_permission(user, 'TICKET_MODIFY') self.env.config.set('ticket', 'restrict_owner', True) return ticket def test_get_allowed_owners_returns_set_owner_list(self): """Users specified in `set_owner` for the action are returned.""" req = None action = {'set_owner': ['user4', 'user5']} ticket = self._test_get_allowed_owners() self.assertEqual(['user4', 'user5'], self.ctlr.get_allowed_owners(req, ticket, action)) def test_get_allowed_owners_returns_user_with_ticket_modify(self): """Users with TICKET_MODIFY are are returned if `set_owner` is not specified for the action. """ req = None action = {} ticket = self._test_get_allowed_owners() self.assertEqual(['user1', 'user3'], self.ctlr.get_allowed_owners(req, ticket, action)) def test_status_change_with_operation(self): """Status change with operation.""" ticket = Ticket(self.env) ticket['new'] = 'status1' ticket['owner'] = 'user1' ticket.insert() req = MockRequest(self.env, path_info='/ticket', authname='user2', method='POST') label, control, hints = \ self.ctlr.render_ticket_action_control(req, ticket, 'accept') self.assertEqual('accept', label) self.assertEqual('', unicode(control)) self.assertEqual( "The owner will be <span class=\"trac-author-user\">" "user2</span>. The status will be 'accepted'.", unicode(hints)) def test_status_change_with_no_operation(self): """Existing ticket status change with no operation.""" config = self.env.config config.set('ticket-workflow', 'change_status', 'status1 -> status2') self._reload_workflow() ticket = Ticket(self.env) ticket['status'] = 'status1' ticket.insert() req = MockRequest(self.env, path_info='/ticket', method='POST') label, control, hints = \ self.ctlr.render_ticket_action_control(req, ticket, 'change_status') self.assertEqual('change status', label) self.assertEqual('', unicode(control)) self.assertEqual("Next status will be 'status2'.", unicode(hints)) def test_new_ticket_status_change_with_no_operation(self): """New ticket status change with no operation.""" config = self.env.config config.set('ticket-workflow', 'change_status', '<none> -> status1') self._reload_workflow() ticket = Ticket(self.env) req = MockRequest(self.env, path_info='/newticket', method='POST') label, control, hints = \ self.ctlr.render_ticket_action_control(req, ticket, 'change_status') self.assertEqual('change status', label) self.assertEqual('', unicode(control)) self.assertEqual("The status will be 'status1'.", unicode(hints)) def test_operation_with_no_status_change(self): """Operation with no status change.""" config = self.env.config config.set('ticket-workflow', 'change_owner', 'closed -> closed') config.set('ticket-workflow', 'change_owner.operations', 'set_owner') self._reload_workflow() ticket = Ticket(self.env) ticket['status'] = 'closed' ticket['owner'] = 'user2' ticket.insert() req = MockRequest(self.env, path_info='/ticket', method='POST', authname='user1') label, control, hints = \ self.ctlr.render_ticket_action_control(req, ticket, 'change_owner') self.assertEqual('change owner', label) self.assertEqual( 'to <input id="action_change_owner_reassign_owner" ' 'name="action_change_owner_reassign_owner" type="text" ' 'value="user1" />', unicode(control)) self.assertEqual( 'The owner will be changed from <span class="trac-author">' 'user2</span> to the specified user.', unicode(hints)) def test_transition_to_star(self): """Action not rendered by CTW for transition to * AdvancedTicketWorkflow uses the behavior for the triage operation (see #12823) """ config = self.env.config config.set('ticket-workflow', 'create_and_triage', '<none> -> *') config.set('ticket-workflow', 'create_and_triage.operations', 'triage') self._reload_workflow() ticket = Ticket(self.env) req = MockRequest(self.env, path_info='/newticket', method='POST') actions = self.ctlr.get_ticket_actions(req, ticket) # create_and_triage not in actions self.assertEqual([(1, 'create'), (0, 'create_and_assign')], actions) def test_transition_to_star_with_leave_operation(self): """Action is rendered by CTW for transition to * with leave_status """ config = self.env.config config.set('ticket-workflow', 'change_owner', 'assigned,closed -> *') config.set('ticket-workflow', 'change_owner.operations', 'leave_status,set_owner') self._reload_workflow() status = ['assigned', 'closed'] for s in status: ticket = Ticket(self.env) ticket['status'] = s ticket['owner'] = 'user2' ticket.insert() req = MockRequest(self.env, path_info='/ticket', method='POST', authname='user1') label, control, hints = \ self.ctlr.render_ticket_action_control(req, ticket, 'change_owner') self.assertEqual('change owner', label) self.assertEqual( 'to <input id="action_change_owner_reassign_owner" ' 'name="action_change_owner_reassign_owner" type="text" ' 'value="user1" />', unicode(control)) self.assertEqual( 'The owner will be changed from <span class="trac-author">' 'user2</span> to the specified user.', unicode(hints)) def test_leave_operation(self): ticket = Ticket(self.env) ticket['status'] = 'assigned' ticket['owner'] = 'user2' ticket.insert() req = MockRequest(self.env, path_info='/ticket', method='POST', authname='user1') label, control, hints = \ self.ctlr.render_ticket_action_control(req, ticket, 'leave') self.assertEqual('leave', label) self.assertEqual('as assigned', unicode(control)) self.assertEqual( 'The owner will remain <span class="trac-author">' 'user2</span>.', unicode(hints)) def test_get_actions_by_operation_for_req(self): """Request with no permission checking.""" req = MockRequest(self.env, path_info='/ticket/1') ticket = insert_ticket(self.env, status='new') actions = self.ctlr.get_actions_by_operation_for_req( req, ticket, 'set_owner') self.assertEqual([(0, u'change_owner'), (0, u'reassign')], actions) def test_get_actions_by_operation_for_req_with_ticket_modify(self): """User without TICKET_MODIFY won't have reassign action.""" req = MockRequest(self.env, authname='user1', path_info='/ticket/1') ticket = insert_ticket(self.env, status='new') actions = self.ctlr.get_actions_by_operation_for_req( req, ticket, 'set_owner') self.assertEqual([(0, u'change_owner')], actions) def test_get_actions_by_operation_for_req_without_ticket_modify(self): """User with TICKET_MODIFY will have reassign action.""" PermissionSystem(self.env).grant_permission('user1', 'TICKET_MODIFY') req = MockRequest(self.env, authname='user1', path_info='/ticket/1') ticket = insert_ticket(self.env, status='new') actions = self.ctlr.get_actions_by_operation_for_req( req, ticket, 'set_owner') self.assertEqual([(0, u'change_owner'), (0, u'reassign')], actions)