class AdvancedTicketWorkflowTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True, enable=['trac.*', 'advancedworkflow.*']) self.tktmod = TicketModule(self.env) def tearDown(self): self.env.reset_db() def _config_set(self, section, entries): for option, value in entries: self.env.config.set(section, option, value) def _insert_ticket(self, when=None, **values): values.setdefault('status', 'new') values.setdefault('type', 'defect') ticket = Ticket(self.env) ticket.populate(values) return ticket.insert(when=when) def _insert_component(self, name, owner): component = model.Component(self.env) component.name = name component.owner = owner component.insert() def _post_req(self, action, ticket): form_token = 'x' * 40 args = { 'action': action, 'submit': '1', '__FORM_TOKEN': form_token, 'view_time': str(to_utimestamp(ticket['changetime'])) } args.update( ('field_' + f['name'], ticket[f['name']]) for f in ticket.fields) return MockRequest(self.env, method='POST', form_token=form_token, path_info='/ticket/%d' % ticket.id, args=args) def test_set_owner_to_reporter(self): self.env.config.set( 'ticket', 'workflow', 'ConfigurableTicketWorkflow,TicketWorkflowOpOwnerReporter') self._config_set('ticket-workflow', [ ('needinfo', '* -> needinfo'), ('needinfo.name', 'Need info'), ('needinfo.operations', 'set_owner_to_reporter'), ]) tktid = self._insert_ticket(summary='set owner to reporter', reporter='john', owner='joe') ticket = Ticket(self.env, tktid) req = self._post_req('needinfo', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('john', ticket['owner']) self.assertEqual('needinfo', ticket['status']) def test_set_owner_to_component_owner(self): self.env.config.set( 'ticket', 'workflow', 'ConfigurableTicketWorkflow,TicketWorkflowOpOwnerComponent') self._config_set('ticket-workflow', [ ('to-c-owner', '* -> assigned'), ('to-c-owner.operations', 'set_owner_to_component_owner'), ]) self._insert_component('component3', 'foo') tktid = self._insert_ticket(summary='set owner to component owner', reporter='anonymous', owner='joe', component='component3') ticket = Ticket(self.env, tktid) req = self._post_req('to-c-owner', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('foo', ticket['owner']) self.assertEqual('assigned', ticket['status']) def test_set_owner_to_component_owner_with_missing_component(self): self.env.config.set( 'ticket', 'workflow', 'ConfigurableTicketWorkflow,TicketWorkflowOpOwnerComponent') self._config_set('ticket-workflow', [ ('to-c-owner', '* -> assigned'), ('to-c-owner.operations', 'set_owner_to_component_owner'), ]) tktid = self._insert_ticket(summary='set owner to component owner', reporter='anonymous', owner='joe', component='component3') ticket = Ticket(self.env, tktid) req = self._post_req('to-c-owner', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('', ticket['owner']) self.assertEqual('assigned', ticket['status']) def test_set_owner_to_field(self): self.env.config.set( 'ticket', 'workflow', 'ConfigurableTicketWorkflow,TicketWorkflowOpOwnerField') self._config_set('ticket-workflow', [ ('to-owner', '* -> assigned'), ('to-owner.operations', 'set_owner_to_field'), ('to-owner.set_owner_to_field', 'keywords'), ]) tktid = self._insert_ticket(summary='set owner to field', reporter='anonymous', owner='joe', keywords='john') ticket = Ticket(self.env, tktid) req = self._post_req('to-owner', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('john', ticket['owner']) self.assertEqual('assigned', ticket['status']) def test_set_owner_to_previous(self): self.env.config.set( 'ticket', 'workflow', 'ConfigurableTicketWorkflow,TicketWorkflowOpOwnerPrevious') self._config_set('ticket-workflow', [ ('to-prev', '* -> assigned'), ('to-prev.operations', 'set_owner_to_previous'), ]) tktid = self._insert_ticket(when=datetime(2017, 3, 9, tzinfo=utc), summary='set owner to previous', reporter='anonymous', owner='joe') ticket = Ticket(self.env, tktid) req = self._post_req('to-prev', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('joe', ticket['owner']) self.assertEqual('assigned', ticket['status']) ticket = Ticket(self.env, tktid) ticket['owner'] = 'alice' ticket.save_changes(when=datetime(2017, 3, 9, 1, tzinfo=utc)) ticket['owner'] = 'john' ticket.save_changes(when=datetime(2017, 3, 9, 2, tzinfo=utc)) ticket = Ticket(self.env, tktid) req = self._post_req('to-prev', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('alice', ticket['owner']) self.assertEqual('assigned', ticket['status']) ticket = Ticket(self.env, tktid) req = self._post_req('to-prev', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('john', ticket['owner']) self.assertEqual('assigned', ticket['status']) def test_set_status_to_previous(self): self.env.config.set( 'ticket', 'workflow', 'ConfigurableTicketWorkflow,TicketWorkflowOpStatusPrevious') self._config_set('ticket-workflow', [ ('revert-status', '* -> *'), ('revert-status.operations', 'set_status_to_previous'), ]) tktid = self._insert_ticket(when=datetime(2017, 3, 9, tzinfo=utc), summary='set status to previous', reporter='anonymous', owner='joe') ticket = Ticket(self.env, tktid) req = self._post_req('revert-status', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('new', ticket['status']) ticket = Ticket(self.env, tktid) ticket['status'] = 'assigned' ticket.save_changes(when=datetime(2017, 3, 9, 1, tzinfo=utc)) ticket['status'] = 'closed' ticket.save_changes(when=datetime(2017, 3, 9, 2, tzinfo=utc)) ticket = Ticket(self.env, tktid) req = self._post_req('revert-status', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('assigned', ticket['status']) def test_reset_milestone(self): self.env.config.set( 'ticket', 'workflow', 'ConfigurableTicketWorkflow,TicketWorkflowOpResetMilestone') self._config_set('ticket-workflow', [ ('reset-milestone', '* -> *'), ('reset-milestone.operations', 'reset_milestone'), ]) tktid = self._insert_ticket(when=datetime(2017, 3, 9, tzinfo=utc), summary='reset milestone', milestone='milestone1', reporter='anonymous', owner='joe') ticket = Ticket(self.env, tktid) req = self._post_req('reset-milestone', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('milestone1', ticket['milestone']) milestone = Milestone(self.env, ticket['milestone']) milestone.completed = datetime(2017, 3, 8, tzinfo=utc) milestone.update() req = self._post_req('reset-milestone', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('', ticket['milestone']) ticket['milestone'] = 'unknown-milestone' ticket.save_changes(when=datetime(2017, 3, 8, 1, tzinfo=utc)) req = self._post_req('reset-milestone', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('unknown-milestone', ticket['milestone'])
class AdvancedTicketWorkflowTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True, enable=['trac.*', 'advancedworkflow.*']) self.tktmod = TicketModule(self.env) def tearDown(self): self.env.reset_db() def _config_set(self, section, entries): for option, value in entries: self.env.config.set(section, option, value) def _insert_ticket(self, when=None, **values): values.setdefault('status', 'new') values.setdefault('type', 'defect') ticket = Ticket(self.env) ticket.populate(values) return ticket.insert(when=when) def _insert_component(self, name, owner): component = model.Component(self.env) component.name = name component.owner = owner component.insert() def _post_req(self, action, ticket): form_token = 'x' * 40 args = {'action': action, 'submit': '1', '__FORM_TOKEN': form_token, 'view_time': str(to_utimestamp(ticket['changetime']))} args.update(('field_' + f['name'], ticket[f['name']]) for f in ticket.fields) return MockRequest(self.env, method='POST', form_token=form_token, path_info='/ticket/%d' % ticket.id, args=args) def test_set_owner_to_reporter(self): self.env.config.set('ticket', 'workflow', 'ConfigurableTicketWorkflow,TicketWorkflowOpOwnerReporter') self._config_set('ticket-workflow', [ ('needinfo', '* -> needinfo'), ('needinfo.name', 'Need info'), ('needinfo.operations', 'set_owner_to_reporter'), ]) tktid = self._insert_ticket(summary='set owner to reporter', reporter='john', owner='joe') ticket = Ticket(self.env, tktid) req = self._post_req('needinfo', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('john', ticket['owner']) self.assertEqual('needinfo', ticket['status']) def test_set_owner_to_component_owner(self): self.env.config.set('ticket', 'workflow', 'ConfigurableTicketWorkflow,TicketWorkflowOpOwnerComponent') self._config_set('ticket-workflow', [ ('to-c-owner', '* -> assigned'), ('to-c-owner.operations', 'set_owner_to_component_owner'), ]) self._insert_component('component3', 'foo') tktid = self._insert_ticket(summary='set owner to component owner', reporter='anonymous', owner='joe', component='component3') ticket = Ticket(self.env, tktid) req = self._post_req('to-c-owner', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('foo', ticket['owner']) self.assertEqual('assigned', ticket['status']) def test_set_owner_to_component_owner_with_missing_component(self): self.env.config.set('ticket', 'workflow', 'ConfigurableTicketWorkflow,TicketWorkflowOpOwnerComponent') self._config_set('ticket-workflow', [ ('to-c-owner', '* -> assigned'), ('to-c-owner.operations', 'set_owner_to_component_owner'), ]) tktid = self._insert_ticket(summary='set owner to component owner', reporter='anonymous', owner='joe', component='component3') ticket = Ticket(self.env, tktid) req = self._post_req('to-c-owner', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('', ticket['owner']) self.assertEqual('assigned', ticket['status']) def test_set_owner_to_field(self): self.env.config.set('ticket', 'workflow', 'ConfigurableTicketWorkflow,TicketWorkflowOpOwnerField') self._config_set('ticket-workflow', [ ('to-owner', '* -> assigned'), ('to-owner.operations', 'set_owner_to_field'), ('to-owner.set_owner_to_field', 'keywords'), ]) tktid = self._insert_ticket(summary='set owner to field', reporter='anonymous', owner='joe', keywords='john') ticket = Ticket(self.env, tktid) req = self._post_req('to-owner', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('john', ticket['owner']) self.assertEqual('assigned', ticket['status']) def test_set_owner_to_previous(self): self.env.config.set('ticket', 'workflow', 'ConfigurableTicketWorkflow,TicketWorkflowOpOwnerPrevious') self._config_set('ticket-workflow', [ ('to-prev', '* -> assigned'), ('to-prev.operations', 'set_owner_to_previous'), ]) tktid = self._insert_ticket(when=datetime(2017, 3, 9, tzinfo=utc), summary='set owner to previous', reporter='anonymous', owner='joe') ticket = Ticket(self.env, tktid) req = self._post_req('to-prev', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('joe', ticket['owner']) self.assertEqual('assigned', ticket['status']) ticket = Ticket(self.env, tktid) ticket['owner'] = 'alice' ticket.save_changes(when=datetime(2017, 3, 9, 1, tzinfo=utc)) ticket['owner'] = 'john' ticket.save_changes(when=datetime(2017, 3, 9, 2, tzinfo=utc)) ticket = Ticket(self.env, tktid) req = self._post_req('to-prev', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('alice', ticket['owner']) self.assertEqual('assigned', ticket['status']) ticket = Ticket(self.env, tktid) req = self._post_req('to-prev', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('john', ticket['owner']) self.assertEqual('assigned', ticket['status']) def test_set_status_to_previous(self): self.env.config.set('ticket', 'workflow', 'ConfigurableTicketWorkflow,TicketWorkflowOpStatusPrevious') self._config_set('ticket-workflow', [ ('revert-status', '* -> *'), ('revert-status.operations', 'set_status_to_previous'), ]) tktid = self._insert_ticket(when=datetime(2017, 3, 9, tzinfo=utc), summary='set status to previous', reporter='anonymous', owner='joe') ticket = Ticket(self.env, tktid) req = self._post_req('revert-status', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('new', ticket['status']) ticket = Ticket(self.env, tktid) ticket['status'] = 'assigned' ticket.save_changes(when=datetime(2017, 3, 9, 1, tzinfo=utc)) ticket['status'] = 'closed' ticket.save_changes(when=datetime(2017, 3, 9, 2, tzinfo=utc)) ticket = Ticket(self.env, tktid) req = self._post_req('revert-status', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('assigned', ticket['status']) def test_reset_milestone(self): self.env.config.set('ticket', 'workflow', 'ConfigurableTicketWorkflow,TicketWorkflowOpResetMilestone') self._config_set('ticket-workflow', [ ('reset-milestone', '* -> *'), ('reset-milestone.operations', 'reset_milestone'), ]) tktid = self._insert_ticket(when=datetime(2017, 3, 9, tzinfo=utc), summary='reset milestone', milestone='milestone1', reporter='anonymous', owner='joe') ticket = Ticket(self.env, tktid) req = self._post_req('reset-milestone', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('milestone1', ticket['milestone']) milestone = Milestone(self.env, ticket['milestone']) milestone.completed = datetime(2017, 3, 8, tzinfo=utc) milestone.update() req = self._post_req('reset-milestone', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('', ticket['milestone']) ticket['milestone'] = 'unknown-milestone' ticket.save_changes(when=datetime(2017, 3, 8, 1, tzinfo=utc)) req = self._post_req('reset-milestone', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('unknown-milestone', ticket['milestone'])
class TicketModuleTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() self.ticket_module = TicketModule(self.env) def tearDown(self): self.env.reset_db() def _create_ticket_with_change(self, old_props, new_props, author='anonymous'): """Create a ticket with `old_props` and apply properties in `new_props`. """ t = Ticket(self.env) t.populate(old_props) t.insert() comment = new_props.pop('comment', None) t.populate(new_props) t.save_changes(author, comment=comment) return t def _insert_ticket(self, **kw): """Helper for inserting a ticket into the database""" ticket = Ticket(self.env) for k, v in kw.items(): ticket[k] = v return ticket.insert() def test_ticket_module_as_default_handler(self): """The New Ticket mainnav entry is active when TicketModule is the `default_handler` and navigating to the base url. Test for regression of http://trac.edgewall.org/ticket/8791. """ req = MockRequest(self.env) chrome = Chrome(self.env).prepare_request(req, self.ticket_module) name = None for item in chrome['nav']['mainnav']: if item['active'] is True: name = item['name'] break self.assertEqual('newticket', name) def test_reporter_and_owner_full_name_is_displayed(self): """Full name of reporter and owner are used in ticket properties.""" self.env.insert_users([('user1', 'User One', ''), ('user2', 'User Two', '')]) tkt_id = self._insert_ticket(reporter='user1', owner='user2') PermissionSystem(self.env).grant_permission('user2', 'TICKET_VIEW') req = MockRequest(self.env, authname='user2', method='GET', args={'id': tkt_id, 'replyto': '1'}) data = self.ticket_module.process_request(req)[1] self.assertEqual(u'<a class="trac-author" href="/trac.cgi/query?' u'status=!closed&reporter=user1">User One</a>', unicode(data['reporter_link'])) self.assertEqual(u'<a class="trac-author-user" href="/trac.cgi/query?' u'status=!closed&owner=user2">User Two</a>', unicode(data['owner_link'])) def test_quoted_reply_author_is_obfuscated(self): """Reply-to author is obfuscated in a quoted reply.""" author = 'author <*****@*****.**>' tkt = self._create_ticket_with_change({}, {'comment': 'the comment'}, author) req = MockRequest(self.env, method='GET', args={'id': tkt.id, 'replyto': '1'}) data = self.ticket_module.process_request(req)[1] comment = u"Replying to [comment:1 author <author@\u2026>]:\n> " \ u"the comment\n" self.assertEqual(comment, data['comment']) self.assertEqual(comment, data['change_preview']['comment']) def test_quoted_reply_author_full_name_is_displayed(self): """Full name of reply-to author is used in quoted reply.""" self.env.insert_users([('author', 'The Author', '*****@*****.**')]) tkt = self._create_ticket_with_change({}, {'comment': 'the comment'}, 'author') req = MockRequest(self.env, method='GET', args={'id': tkt.id, 'replyto': '1'}) data = self.ticket_module.process_request(req)[1] comment = u"Replying to [comment:1 The Author]:\n> " \ u"the comment\n" self.assertEqual(comment, data['comment']) self.assertEqual(comment, data['change_preview']['comment']) def test_ticket_property_diff_owner_change(self): """Property diff message when ticket owner is changed.""" t = self._create_ticket_with_change({'owner': 'owner1'}, {'owner': 'owner2'}) req = MockRequest(self.env, args={'id': t.id}) data = self.ticket_module.process_request(req)[1] field = data['changes'][0]['fields']['owner'] self.assertEqual("changed from <em>owner1</em> to <em>owner2</em>", str(field['rendered'])) def test_ticket_property_diff_owner_add(self): """Property diff message when ticket owner is added.""" t = self._create_ticket_with_change({'owner': ''}, {'owner': 'owner2'}) req = MockRequest(self.env, args={'id': t.id}) data = self.ticket_module.process_request(req)[1] field = data['changes'][0]['fields']['owner'] self.assertEqual("set to <em>owner2</em>", str(field['rendered'])) def test_ticket_property_diff_owner_remove(self): """Property diff message when ticket owner is removed.""" t = self._create_ticket_with_change({'owner': 'owner1'}, {'owner': ''}) req = MockRequest(self.env, args={'id': t.id}) data = self.ticket_module.process_request(req)[1] field = data['changes'][0]['fields']['owner'] self.assertEqual("<em>owner1</em> deleted", str(field['rendered'])) def test_ticket_property_diff_reporter_change(self): """Property diff message when ticket reporter is changed.""" t = self._create_ticket_with_change({'reporter': 'reporter1'}, {'reporter': 'reporter2'}) req = MockRequest(self.env, args={'id': t.id}) data = self.ticket_module.process_request(req)[1] field = data['changes'][0]['fields']['reporter'] self.assertEqual("changed from <em>reporter1</em> to " "<em>reporter2</em>", str(field['rendered'])) def test_ticket_property_diff_reporter_add(self): """Property diff message when ticket reporter is added.""" t = self._create_ticket_with_change({'reporter': ''}, {'reporter': 'reporter2'}) req = MockRequest(self.env, args={'id': t.id}) data = self.ticket_module.process_request(req)[1] field = data['changes'][0]['fields']['reporter'] self.assertEqual("set to <em>reporter2</em>", str(field['rendered'])) def test_ticket_property_diff_reporter_remove(self): """Property diff message when ticket reporter is removed.""" t = self._create_ticket_with_change({'reporter': 'reporter1'}, {'reporter': ''}) req = MockRequest(self.env, args={'id': t.id}) data = self.ticket_module.process_request(req)[1] field = data['changes'][0]['fields']['reporter'] self.assertEqual("<em>reporter1</em> deleted", str(field['rendered'])) def _test_invalid_cnum_raises(self, action, cnum=None): self._insert_ticket() req = MockRequest(self.env, args={'action': action, 'id': '1'}) if cnum is not None: req.args.update({'cnum': cnum}) self.assertRaises(HTTPBadRequest, self.ticket_module.process_request, req) def test_comment_history_cnum_missing_raises(self): self._test_invalid_cnum_raises('comment-history') def test_comment_history_cnum_invalid_type_raises(self): self._test_invalid_cnum_raises('comment-history', 'a') def test_comment_history_cnum_empty_raises(self): self._test_invalid_cnum_raises('comment-history', '') def test_comment_history_cnum_out_of_range(self): """Out of range cnum returns an empty history.""" self._insert_ticket() req = MockRequest(self.env, args={'action': 'comment-history', 'id': '1', 'cnum': '1'}) resp = self.ticket_module.process_request(req) self.assertEqual([], resp[1]['history']) def test_comment_diff_cnum_missing_raises(self): self._test_invalid_cnum_raises('comment-diff') def test_comment_diff_cnum_invalid_type_raises(self): self._test_invalid_cnum_raises('comment-diff', 'a') def test_comment_diff_cnum_empty_raises(self): self._test_invalid_cnum_raises('comment-diff', '') def test_comment_diff_cnum_out_of_range_raises(self): self._insert_ticket() req = MockRequest(self.env, args={'action': 'comment-diff', 'id': '1', 'cnum': '1'}) self.assertRaises(ResourceNotFound, self.ticket_module.process_request, req) def _test_template_data_for_time_field(self, req, value, expected, format): self.env.config.set('ticket-custom', 'timefield', 'time') if format: self.env.config.set('ticket-custom', 'timefield.format', format) self._insert_ticket(summary='Time fields', timefield=value) self.assertEqual(value, Ticket(self.env, 1)['timefield']) self.assertTrue(self.ticket_module.match_request(req)) data = self.ticket_module.process_request(req)[1] for f in data['fields']: if f['name'] == 'timefield': self.assertEqual(expected, f['edit']) break else: self.fail('Missing timefield field') def test_template_data_for_time_field_with_formats(self): gmt12 = timezone('GMT +12:00') req = MockRequest(self.env, method='GET', path_info='/ticket/1', tz=gmt12) value = datetime(2016, 1, 2, 23, 34, 45, tzinfo=utc) expected = user_time(req, format_datetime, value) self.assertIn('11', expected) # check 11 in hour part self._test_template_data_for_time_field(req, value, expected, None) self._test_template_data_for_time_field(req, value, expected, 'datetime') self._test_template_data_for_time_field(req, value, expected, 'relative') def test_template_data_for_time_field_with_date_format(self): value = datetime(2016, 2, 22, 22, 22, 22, tzinfo=utc) self.env.config.set('ticket-custom', 'timefield', 'time') self.env.config.set('ticket-custom', 'timefield.format', 'date') self._insert_ticket(summary='Time fields', timefield=value) self.assertEqual(value, Ticket(self.env, 1)['timefield']) gmt12 = timezone('GMT +12:00') req = MockRequest(self.env, method='GET', path_info='/ticket/1', tz=gmt12) expected = user_time(req, format_date, value) self.assertIn('23', expected) # check 23 in day part self.assertTrue(self.ticket_module.match_request(req)) data = self.ticket_module.process_request(req)[1] for f in data['fields']: if f['name'] == 'timefield': self.assertEqual(expected, f['edit']) break else: self.fail('Missing timefield field') def test_template_data_for_invalid_time_field(self): self.env.config.set('ticket-custom', 'timefield', 'time') self._insert_ticket(summary='Time fields', timefield=datetime_now(utc)) self.env.db_transaction("UPDATE ticket_custom SET value='invalid' " "WHERE ticket=1 AND name='timefield'") self.assertEqual(None, Ticket(self.env, 1)['timefield']) req = MockRequest(self.env, method='GET', path_info='/ticket/1') self.assertTrue(self.ticket_module.match_request(req)) data = self.ticket_module.process_request(req)[1] self.assertEqual(None, data['ticket']['timefield']) for f in data['fields']: if f['name'] == 'timefield': self.assertEqual('', f['edit']) break else: self.fail('Missing timefield field') def test_template_data_for_invalid_time_field_on_newticket(self): self.env.config.set('ticket-custom', 'timefield', 'time') req = MockRequest(self.env, method='GET', path_info='/newticket') req.args['timefield'] = 'invalid' self.assertTrue(self.ticket_module.match_request(req)) data = self.ticket_module.process_request(req)[1] self.assertEqual('invalid', data['ticket']['timefield']) for f in data['fields']: if f['name'] == 'timefield': self.assertEqual('invalid', f['edit']) break else: self.fail('Missing timefield field') def test_template_data_changes_for_time_field(self): self.env.config.set('ticket-custom', 'timefield', 'time') dt1 = datetime(2015, 7, 8, tzinfo=utc) dt2 = datetime(2015, 12, 11, tzinfo=utc) with self.env.db_transaction: self._insert_ticket(summary='Time fields', timefield=datetime_now(utc)) self.env.db_transaction("UPDATE ticket_custom SET value='invalid' " "WHERE ticket=1 AND name='timefield'") t = Ticket(self.env, 1) t['timefield'] = dt1 t.save_changes('anonymous') t = Ticket(self.env, 1) t['timefield'] = dt2 t.save_changes('anonymous') req = MockRequest(self.env, method='GET', path_info='/ticket/1') self.assertTrue(self.ticket_module.match_request(req)) data = self.ticket_module.process_request(req)[1] changes = data['changes'] dt1_text = user_time(req, format_datetime, dt1) dt2_text = user_time(req, format_datetime, dt2) self.assertEqual(2, len(changes)) self.assertEqual('', changes[0]['fields']['timefield']['old']) self.assertEqual(dt1_text, changes[0]['fields']['timefield']['new']) self.assertEqual(dt1_text, changes[1]['fields']['timefield']['old']) self.assertEqual(dt2_text, changes[1]['fields']['timefield']['new']) def test_submit_with_time_field(self): self.env.config.set('ticket-custom', 'timefield', 'time') self._insert_ticket(summary='Time fields', timefield='') ticket = Ticket(self.env, 1) args_base = {'submit': '*', 'action': 'leave', 'id': '1', 'field_summary': ticket['summary'], 'field_reporter': ticket['reporter'], 'field_description': ticket['description'], 'view_time': str(to_utimestamp(ticket['changetime']))} for f in ticket.fields: args_base['field_%s' % f['name']] = ticket[f['name']] or '' args = args_base.copy() args['field_timefield'] = 'invalid datetime' req = MockRequest(self.env, method='POST', path_info='/ticket/1', args=args) self.assertTrue(self.ticket_module.match_request(req)) self.ticket_module.process_request(req) warnings = req.chrome['warnings'] self.assertNotEqual([], warnings) self.assertEqual(1, len(warnings)) self.assertIn('is an invalid date, or the date format is not known.', unicode(warnings[0])) ticket = Ticket(self.env, 1) self.assertEqual(None, ticket['timefield']) args = args_base.copy() args['field_timefield'] = '2016-01-02T12:34:56Z' req = MockRequest(self.env, method='POST', path_info='/ticket/1', args=args) self.assertTrue(self.ticket_module.match_request(req)) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, 1) self.assertEqual(datetime(2016, 1, 2, 12, 34, 56, tzinfo=utc), ticket['timefield']) def _test_render_time_field(self, format, req, value, expected): self.env.config.set('ticket-custom', 'timefield', 'time') self.env.config.set('ticket-custom', 'timefield.format', format) def timefield_text(): self.assertTrue(self.ticket_module.match_request(req)) rv = self.ticket_module.process_request(req) stream = Chrome(self.env).render_template(req, rv[0], rv[1], rv[2], fragment=True) stream = stream.select('//td[@headers="h_timefield"]') return stream.render('text', encoding=None).strip() self._insert_ticket(summary='Time fields') self.assertEqual('', timefield_text()) ticket = Ticket(self.env, 1) ticket['timefield'] = value ticket.save_changes('anonymous') self.assertEqual(expected, timefield_text()) def test_render_time_field_date(self): req = MockRequest(self.env, method='GET', path_info='/ticket/1') value = datetime(2015, 7, 8, tzinfo=utc) expected = user_time(req, format_date, value) self._test_render_time_field('date', req, value, expected) def test_render_time_field_datetime(self): req = MockRequest(self.env, method='GET', path_info='/ticket/1') value = datetime(2015, 7, 8, 12, 34, 56, tzinfo=utc) expected = user_time(req, format_datetime, value) self._test_render_time_field('datetime', req, value, expected) def test_render_time_field_relative(self): req = MockRequest(self.env, method='GET', path_info='/ticket/1') value = datetime_now(utc) - timedelta(days=1) self._test_render_time_field('relative', req, value, '24 hours ago') def _test_newticket_with_enum_as_custom_field(self, field_name): self.env.config.set('ticket-custom', field_name, 'text') self.env.config.set('ticket-custom', '%s.label' % field_name, '(%s)' % field_name) with self.env.db_transaction as db: if field_name in ('milestone', 'component', 'version'): db("DELETE FROM %s" % field_name) elif field_name == 'type': db("DELETE FROM enum WHERE type='ticket_type'") else: db("DELETE FROM enum WHERE type=%s", (field_name,)) tktsys = TicketSystem(self.env) tktsys.reset_ticket_fields() del tktsys.custom_fields req = MockRequest(self.env, path_info='/newticket') self.assertEqual(True, self.ticket_module.match_request(req)) resp = self.ticket_module.process_request(req) for field in resp[1]['fields']: if field['name'] == field_name: self.assertEqual('(%s)' % field_name, field['label']) self.assertTrue(field['custom']) self.assertFalse(field['options']) self.assertFalse(field.get('optgroups')) break else: self.fail('Missing %s in fields' % field_name) def test_newticket_with_component_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('component') def test_newticket_with_milestone_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('milestone') def test_newticket_with_priority_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('priority') def test_newticket_with_resolution_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('resolution') def test_newticket_with_severity_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('severity') def test_newticket_with_type_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('type') def test_newticket_with_version_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('version')
class CustomFieldMaxSizeTestCase(unittest.TestCase): """Tests for [ticket-custom] max_size attribute.""" def setUp(self): self.env = EnvironmentStub() self.ticket_module = TicketModule(self.env) def tearDown(self): self.env.reset_db() def _setup_env_and_req(self, max_size, field_value): self.env.config.set('ticket-custom', 'text1', 'text') self.env.config.set('ticket-custom', 'text1.max_size', max_size) ticket = insert_ticket(self.env, summary='summary', text1='init') change_time = Ticket(self.env, ticket.id)['changetime'] view_time = str(to_utimestamp(change_time)) req = MockRequest(self.env, method='POST', path_info='/ticket/%d' % ticket.id, args={ 'submit': 'Submit changes', 'field_text1': field_value, 'action': 'leave', 'view_time': view_time }) return req def test_ticket_custom_field_greater_than_max_size(self): """Validation fails for a ticket custom field with content length greater than max_size. """ max_size = 5 field_value = 'a' * (max_size + 1) req = self._setup_env_and_req(max_size, field_value) self.assertTrue(self.ticket_module.match_request(req)) self.ticket_module.process_request(req) self.assertTrue(req.args['preview']) self.assertEqual(1, len(req.chrome['warnings'])) self.assertIn( "The ticket field <strong>Text1</strong> is invalid: " "Must be less than or equal to 5 characters", unicode(req.chrome['warnings'][0])) def test_ticket_custom_field_less_than_max_size(self): """Validation succeeds for a ticket custom field with content length less than or equal to max_size. """ max_size = 5 field_value = 'a' * max_size req = self._setup_env_and_req(max_size, field_value) self.assertTrue(self.ticket_module.match_request(req)) with self.assertRaises(RequestDone): self.ticket_module.process_request(req) self.assertEqual(0, len(req.chrome['warnings'])) self.assertEqual(field_value, Ticket(self.env, 1)['text1']) def test_ticket_custom_field_max_size_is_zero(self): """Validation is skipped when max_size attribute is <= 0.""" max_size = 0 field_value = 'a' * 100 req = self._setup_env_and_req(max_size, field_value) self.assertTrue(self.ticket_module.match_request(req)) with self.assertRaises(RequestDone): self.ticket_module.process_request(req) self.assertEqual(0, len(req.chrome['warnings'])) self.assertEqual(field_value, Ticket(self.env, 1)['text1'])
class TicketModuleTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() self.env.config.set( 'trac', 'permission_policies', 'DefaultTicketPolicy, DefaultPermissionPolicy, ' 'LegacyAttachmentPolicy') self.ticket_module = TicketModule(self.env) def tearDown(self): self.env.reset_db() def _create_ticket_with_change(self, old_props, new_props, author='anonymous'): """Create a ticket with `old_props` and apply properties in `new_props`. """ t = insert_ticket(self.env, **old_props) comment = new_props.pop('comment', None) t.populate(new_props) t.save_changes(author, comment=comment) return t def _has_auto_preview(self, req): return any('/trac.cgi/chrome/common/js/auto_preview.js' in s['href'] for s in req.chrome['scripts']) def _insert_ticket(self, **kw): """Helper for inserting a ticket into the database""" return insert_ticket(self.env, **kw) def test_get_moved_attributes(self): """The attributes `max_comment_size`, `max_description_size` and `max_summary_size` have been moved to TicketSystem but are accessible on TicketModule for backward compatibility. """ ts = TicketSystem(self.env) tm = TicketModule(self.env) self.assertEqual(ts.max_comment_size, tm.max_comment_size) self.assertEqual(ts.max_description_size, tm.max_description_size) self.assertEqual(ts.max_summary_size, tm.max_summary_size) def test_ticket_module_as_default_handler(self): """The New Ticket mainnav entry is active when TicketModule is the `default_handler` and navigating to the base url. Test for regression of http://trac.edgewall.org/ticket/8791. """ req = MockRequest(self.env) chrome = Chrome(self.env).prepare_request(req, self.ticket_module) name = None for item in chrome['nav']['mainnav']: if item['active'] is True: name = item['name'] break self.assertEqual('newticket', name) def test_reporter_and_owner_full_name_is_displayed(self): """Full name of reporter and owner are used in ticket properties.""" self.env.insert_users([('user1', 'User One', ''), ('user2', 'User Two', '')]) ticket = self._insert_ticket(reporter='user1', owner='user2') PermissionSystem(self.env).grant_permission('user2', 'TICKET_VIEW') req = MockRequest(self.env, authname='user2', method='GET', args={ 'id': ticket.id, 'replyto': '1' }) data = self.ticket_module.process_request(req)[1] self.assertEqual( u'<a class="trac-author" href="/trac.cgi/query?' u'status=!closed&reporter=user1">User One</a>', unicode(data['reporter_link'])) self.assertEqual( u'<a class="trac-author-user" href="/trac.cgi/query?' u'status=!closed&owner=user2">User Two</a>', unicode(data['owner_link'])) def test_version_release_date_displayed(self): """Version release date is shown in ticket properties.""" v1 = Version(self.env) v1.name = 'v1' v1.time = datetime_now(utc) - timedelta(weeks=2) v1.insert() v2 = Version(self.env) v2.name = 'v2' v2.insert() ticket = [ self._insert_ticket(summary='ticket 1', version='v1'), self._insert_ticket(summary='ticket 2', version='v2'), self._insert_ticket(summary='ticket 3', version='v3') ] def version_field(data): for field in data['fields']: if field['name'] == 'version': return field # Version with release data. req = MockRequest(self.env, method='GET', args={'id': ticket[0].id}) data = self.ticket_module.process_request(req)[1] self.assertIn(u'title="Released ', unicode(version_field(data)['rendered'])) # Version without release data. req = MockRequest(self.env, method='GET', args={'id': ticket[1].id}) data = self.ticket_module.process_request(req)[1] self.assertNotIn(u'title="Released ', unicode(version_field(data)['rendered'])) # Non-existent version. req = MockRequest(self.env, method='GET', args={'id': ticket[2].id}) data = self.ticket_module.process_request(req)[1] self.assertNotIn(u'title="Released ', unicode(version_field(data)['rendered'])) def test_quoted_reply_author_is_obfuscated(self): """Reply-to author is obfuscated in a quoted reply.""" author = 'author <*****@*****.**>' tkt = self._create_ticket_with_change({}, {'comment': 'the comment'}, author) req = MockRequest(self.env, method='GET', args={ 'id': tkt.id, 'replyto': '1' }) data = self.ticket_module.process_request(req)[1] comment = u"Replying to [comment:1 author <author@\u2026>]:\n> " \ u"the comment\n" self.assertEqual(comment, data['comment']) self.assertEqual(comment, data['change_preview']['comment']) def test_quoted_reply_author_full_name_is_displayed(self): """Full name of reply-to author is used in quoted reply.""" self.env.insert_users([('author', 'The Author', '*****@*****.**')]) tkt = self._create_ticket_with_change({}, {'comment': 'the comment'}, 'author') req = MockRequest(self.env, method='GET', args={ 'id': tkt.id, 'replyto': '1' }) data = self.ticket_module.process_request(req)[1] comment = u"Replying to [comment:1 The Author]:\n> " \ u"the comment\n" self.assertEqual(comment, data['comment']) self.assertEqual(comment, data['change_preview']['comment']) def test_ticket_property_diff_owner_change(self): """Property diff message when ticket owner is changed.""" t = self._create_ticket_with_change({'owner': 'owner1'}, {'owner': 'owner2'}) req = MockRequest(self.env, args={'id': t.id}) data = self.ticket_module.process_request(req)[1] field = data['changes'][0]['fields']['owner'] self.assertEqual("changed from <em>owner1</em> to <em>owner2</em>", unicode(field['rendered'])) def test_ticket_property_diff_owner_add(self): """Property diff message when ticket owner is added.""" t = self._create_ticket_with_change({'owner': ''}, {'owner': 'owner2'}) req = MockRequest(self.env, args={'id': t.id}) data = self.ticket_module.process_request(req)[1] field = data['changes'][0]['fields']['owner'] self.assertEqual("set to <em>owner2</em>", unicode(field['rendered'])) def test_ticket_property_diff_owner_remove(self): """Property diff message when ticket owner is removed.""" t = self._create_ticket_with_change({'owner': 'owner1'}, {'owner': ''}) req = MockRequest(self.env, args={'id': t.id}) data = self.ticket_module.process_request(req)[1] field = data['changes'][0]['fields']['owner'] self.assertEqual("<em>owner1</em> deleted", unicode(field['rendered'])) def test_ticket_property_diff_reporter_change(self): """Property diff message when ticket reporter is changed.""" t = self._create_ticket_with_change({'reporter': 'reporter1'}, {'reporter': 'reporter2'}) req = MockRequest(self.env, args={'id': t.id}) data = self.ticket_module.process_request(req)[1] field = data['changes'][0]['fields']['reporter'] self.assertEqual( "changed from <em>reporter1</em> to " "<em>reporter2</em>", unicode(field['rendered'])) def test_ticket_property_diff_reporter_add(self): """Property diff message when ticket reporter is added.""" t = self._create_ticket_with_change({'reporter': ''}, {'reporter': 'reporter2'}) req = MockRequest(self.env, args={'id': t.id}) data = self.ticket_module.process_request(req)[1] field = data['changes'][0]['fields']['reporter'] self.assertEqual("set to <em>reporter2</em>", unicode(field['rendered'])) def test_ticket_property_diff_reporter_remove(self): """Property diff message when ticket reporter is removed.""" t = self._create_ticket_with_change({'reporter': 'reporter1'}, {'reporter': ''}) req = MockRequest(self.env, args={'id': t.id}) data = self.ticket_module.process_request(req)[1] field = data['changes'][0]['fields']['reporter'] self.assertEqual("<em>reporter1</em> deleted", unicode(field['rendered'])) def _test_invalid_cnum_raises(self, action, cnum=None): self._insert_ticket() req = MockRequest(self.env, args={'action': action, 'id': '1'}) if cnum is not None: req.args.update({'cnum': cnum}) self.assertRaises(HTTPBadRequest, self.ticket_module.process_request, req) def test_comment_history_cnum_missing_raises(self): self._test_invalid_cnum_raises('comment-history') def test_comment_history_cnum_invalid_type_raises(self): self._test_invalid_cnum_raises('comment-history', 'a') def test_comment_history_cnum_empty_raises(self): self._test_invalid_cnum_raises('comment-history', '') def test_comment_history_cnum_out_of_range(self): """Out of range cnum returns an empty history.""" self._insert_ticket() req = MockRequest(self.env, args={ 'action': 'comment-history', 'id': '1', 'cnum': '1' }) resp = self.ticket_module.process_request(req) self.assertEqual([], resp[1]['history']) def test_comment_diff_cnum_missing_raises(self): self._test_invalid_cnum_raises('comment-diff') def test_comment_diff_cnum_invalid_type_raises(self): self._test_invalid_cnum_raises('comment-diff', 'a') def test_comment_diff_cnum_empty_raises(self): self._test_invalid_cnum_raises('comment-diff', '') def test_comment_diff_cnum_out_of_range_raises(self): self._insert_ticket() req = MockRequest(self.env, args={ 'action': 'comment-diff', 'id': '1', 'cnum': '1' }) self.assertRaises(ResourceNotFound, self.ticket_module.process_request, req) def test_edit_comment_cnum_missing_raises(self): ticket = self._insert_ticket() req = MockRequest(self.env, method='POST', path_info='/ticket/%d' % ticket.id, args={ 'edit_comment': 'Submit changes', 'cnum_edit': '42' }) self.assertTrue(self.ticket_module.match_request(req)) with self.assertRaises(TracError) as cm: self.ticket_module.process_request(req) self.assertEqual('Comment 42 not found', unicode(cm.exception)) def test_edit_comment_validate_max_comment_size(self): """The [ticket] max_comment_size attribute is validated during ticket comment edit. """ perm_sys = PermissionSystem(self.env) perm_sys.grant_permission('user1', 'TICKET_VIEW') perm_sys.grant_permission('user1', 'TICKET_APPEND') self.env.config.set('ticket', 'max_comment_size', 5) ticket = self._insert_ticket(summary='the summary') ticket.save_changes('user1', '12345') req = MockRequest(self.env, method='POST', authname='user1', path_info='/ticket/%d' % ticket.id, args={ 'id': '1', 'edit_comment': True, 'cnum_edit': '1', 'edited_comment': '123456' }) self.assertTrue(self.ticket_module.match_request(req)) self.ticket_module.process_request(req) self.assertEqual( "The ticket comment is invalid: Must be less than or " "equal to 5 characters", unicode(req.chrome['warnings'][0])) def test_preview_comment_validate_max_comment_size(self): """The [ticket] max_comment_size attribute is validated during ticket comment edit preview. """ perm_sys = PermissionSystem(self.env) perm_sys.grant_permission('user1', 'TICKET_VIEW') perm_sys.grant_permission('user1', 'TICKET_APPEND') self.env.config.set('ticket', 'max_comment_size', 5) ticket = self._insert_ticket(summary='the summary') ticket.save_changes('user1', '12345') req = MockRequest(self.env, method='POST', authname='user1', path_info='/ticket/%d' % ticket.id, args={ 'id': '1', 'preview_comment': True, 'cnum_edit': '1', 'edited_comment': '123456' }) self.assertTrue(self.ticket_module.match_request(req)) self.ticket_module.process_request(req) self.assertEqual( "The ticket comment is invalid: Must be less than or " "equal to 5 characters", unicode(req.chrome['warnings'][0])) def _test_template_data_for_time_field(self, req, value, expected, format): self.env.config.set('ticket-custom', 'timefield', 'time') if format: self.env.config.set('ticket-custom', 'timefield.format', format) self._insert_ticket(summary='Time fields', timefield=value) self.assertEqual(value, Ticket(self.env, 1)['timefield']) self.assertTrue(self.ticket_module.match_request(req)) data = self.ticket_module.process_request(req)[1] for f in data['fields']: if f['name'] == 'timefield': self.assertEqual(expected, f['edit']) break else: self.fail('Missing timefield field') def test_template_data_for_time_field_with_formats(self): gmt12 = timezone('GMT +12:00') req = MockRequest(self.env, method='GET', path_info='/ticket/1', tz=gmt12) value = datetime(2016, 1, 2, 23, 34, 45, tzinfo=utc) expected = user_time(req, format_datetime, value) self.assertIn('11', expected) # check 11 in hour part self._test_template_data_for_time_field(req, value, expected, None) self._test_template_data_for_time_field(req, value, expected, 'datetime') self._test_template_data_for_time_field(req, value, expected, 'relative') def test_template_data_for_time_field_with_date_format(self): value = datetime(2016, 2, 22, 22, 22, 22, tzinfo=utc) self.env.config.set('ticket-custom', 'timefield', 'time') self.env.config.set('ticket-custom', 'timefield.format', 'date') self._insert_ticket(summary='Time fields', timefield=value) self.assertEqual(value, Ticket(self.env, 1)['timefield']) gmt12 = timezone('GMT +12:00') req = MockRequest(self.env, method='GET', path_info='/ticket/1', tz=gmt12) expected = user_time(req, format_date, value) self.assertIn('23', expected) # check 23 in day part self.assertTrue(self.ticket_module.match_request(req)) data = self.ticket_module.process_request(req)[1] for f in data['fields']: if f['name'] == 'timefield': self.assertEqual(expected, f['edit']) break else: self.fail('Missing timefield field') def test_template_data_for_invalid_time_field(self): self.env.config.set('ticket-custom', 'timefield', 'time') self._insert_ticket(summary='Time fields', timefield=datetime_now(utc)) self.env.db_transaction("UPDATE ticket_custom SET value='invalid' " "WHERE ticket=1 AND name='timefield'") self.assertIsNone(Ticket(self.env, 1)['timefield']) req = MockRequest(self.env, method='GET', path_info='/ticket/1') self.assertTrue(self.ticket_module.match_request(req)) data = self.ticket_module.process_request(req)[1] self.assertIsNone(data['ticket']['timefield']) for f in data['fields']: if f['name'] == 'timefield': self.assertEqual('', f['edit']) break else: self.fail('Missing timefield field') def test_template_data_for_invalid_time_field_on_newticket(self): self.env.config.set('ticket-custom', 'timefield', 'time') req = MockRequest(self.env, method='GET', path_info='/newticket') req.args['timefield'] = 'invalid' self.assertTrue(self.ticket_module.match_request(req)) data = self.ticket_module.process_request(req)[1] self.assertEqual('invalid', data['ticket']['timefield']) for f in data['fields']: if f['name'] == 'timefield': self.assertEqual('invalid', f['edit']) break else: self.fail('Missing timefield field') def test_template_data_changes_for_time_field(self): self.env.config.set('ticket-custom', 'timefield', 'time') dt1 = datetime(2015, 7, 8, tzinfo=utc) dt2 = datetime(2015, 12, 11, tzinfo=utc) with self.env.db_transaction: self._insert_ticket(summary='Time fields', timefield=datetime_now(utc)) self.env.db_transaction(""" UPDATE ticket_custom SET value='invalid' WHERE ticket=1 AND name='timefield' """) t = Ticket(self.env, 1) t['timefield'] = dt1 t.save_changes('anonymous') t = Ticket(self.env, 1) t['timefield'] = dt2 t.save_changes('anonymous') req = MockRequest(self.env, method='GET', path_info='/ticket/1') self.assertTrue(self.ticket_module.match_request(req)) data = self.ticket_module.process_request(req)[1] changes = data['changes'] dt1_text = user_time(req, format_datetime, dt1) dt2_text = user_time(req, format_datetime, dt2) self.assertEqual(2, len(changes)) self.assertEqual('', changes[0]['fields']['timefield']['old']) self.assertEqual(dt1_text, changes[0]['fields']['timefield']['new']) self.assertEqual(dt1_text, changes[1]['fields']['timefield']['old']) self.assertEqual(dt2_text, changes[1]['fields']['timefield']['new']) def test_submit_with_time_field(self): self.env.config.set('ticket-custom', 'timefield', 'time') self._insert_ticket(summary='Time fields', timefield='') ticket = Ticket(self.env, 1) args_base = { 'submit': '*', 'action': 'leave', 'id': '1', 'field_summary': ticket['summary'], 'field_reporter': ticket['reporter'], 'field_description': ticket['description'], 'view_time': str(to_utimestamp(ticket['changetime'])) } for f in ticket.fields: args_base['field_%s' % f['name']] = ticket[f['name']] or '' args = args_base.copy() args['field_timefield'] = 'invalid datetime' req = MockRequest(self.env, method='POST', path_info='/ticket/1', args=args) self.assertTrue(self.ticket_module.match_request(req)) self.ticket_module.process_request(req) warnings = req.chrome['warnings'] self.assertNotEqual([], warnings) self.assertEqual(1, len(warnings)) self.assertIn('is an invalid date, or the date format is not known.', unicode(warnings[0])) ticket = Ticket(self.env, 1) self.assertIsNone(ticket['timefield']) args = args_base.copy() args['field_timefield'] = '2016-01-02T12:34:56Z' req = MockRequest(self.env, method='POST', path_info='/ticket/1', args=args) self.assertTrue(self.ticket_module.match_request(req)) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, 1) self.assertEqual(datetime(2016, 1, 2, 12, 34, 56, tzinfo=utc), ticket['timefield']) def _test_render_time_field(self, format, req, value, expected): self.env.config.set('ticket-custom', 'timefield', 'time') self.env.config.set('ticket-custom', 'timefield.format', format) def timefield_text(): self.assertTrue(self.ticket_module.match_request(req)) template, data = self.ticket_module.process_request(req) content = Chrome(self.env).render_fragment(req, template, data) # select('//td[@headers="h_timefield"') replacement class TimefieldExtractor(HTMLTransform): pick_next_text = False value = '' def handle_starttag(self, tag, attrs): if tag == 'td': for name, value in attrs: if name == 'headers' and value == 'h_timefield': self.pick_next_text = True def handle_data(self, data): if self.pick_next_text: self.value += data def handle_endtag(self, tag): if self.pick_next_text: self.pick_next_text = False extractor = TimefieldExtractor(io.BytesIO()) extractor.feed(content.encode('utf-8')) return extractor.value.decode('utf-8').strip() self._insert_ticket(summary='Time fields') self.assertEqual('', timefield_text()) ticket = Ticket(self.env, 1) ticket['timefield'] = value ticket.save_changes('anonymous') self.assertEqual(expected, timefield_text()) def test_render_time_field_date(self): req = MockRequest(self.env, method='GET', path_info='/ticket/1') value = datetime(2015, 7, 8, tzinfo=utc) expected = user_time(req, format_date, value) self._test_render_time_field('date', req, value, expected) def test_render_time_field_datetime(self): req = MockRequest(self.env, method='GET', path_info='/ticket/1') value = datetime(2015, 7, 8, 12, 34, 56, tzinfo=utc) expected = user_time(req, format_datetime, value) self._test_render_time_field('datetime', req, value, expected) def test_render_time_field_relative(self): req = MockRequest(self.env, method='GET', path_info='/ticket/1') value = datetime_now(utc) - timedelta(days=1) self._test_render_time_field('relative', req, value, '24 hours ago') def _test_newticket_with_enum_as_custom_field(self, field_name): self.env.config.set('ticket-custom', field_name, 'text') self.env.config.set('ticket-custom', '%s.label' % field_name, '(%s)' % field_name) with self.env.db_transaction as db: if field_name in ('milestone', 'component', 'version'): db("DELETE FROM %s" % field_name) elif field_name == 'type': db("DELETE FROM enum WHERE type='ticket_type'") else: db("DELETE FROM enum WHERE type=%s", (field_name, )) tktsys = TicketSystem(self.env) tktsys.reset_ticket_fields() del tktsys.custom_fields req = MockRequest(self.env, path_info='/newticket') self.assertTrue(self.ticket_module.match_request(req)) resp = self.ticket_module.process_request(req) for field in resp[1]['fields']: if field['name'] == field_name: self.assertEqual('(%s)' % field_name, field['label']) self.assertTrue(field['custom']) self.assertFalse(field['options']) self.assertFalse(field.get('optgroups')) break else: self.fail('Missing %s in fields' % field_name) def test_newticket_with_component_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('component') def test_newticket_with_milestone_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('milestone') def test_newticket_with_priority_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('priority') def test_newticket_with_resolution_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('resolution') def test_newticket_with_severity_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('severity') def test_newticket_with_type_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('type') def test_newticket_with_version_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('version') def test_add_comment_requires_ticket_append(self): """Adding a ticket comment requires TICKET_APPEND.""" ps = PermissionSystem(self.env) ps.grant_permission('user1', 'TICKET_VIEW') ps.grant_permission('user1', 'TICKET_APPEND') ps.grant_permission('user2', 'TICKET_VIEW') ps.grant_permission('user2', 'TICKET_CHGPROP') ticket = self._insert_ticket(summary='the summary') comment = 'the comment' def make_req(authname): change_time = Ticket(self.env, 1)['changetime'] return MockRequest(self.env, authname=authname, method='POST', path_info='/ticket/1', args={ 'comment': comment, 'action': 'leave', 'submit': True, 'view_time': unicode(to_utimestamp(change_time)) }) req = make_req('user1') self.assertTrue(self.ticket_module.match_request(req)) self.assertRaises(RequestDone, self.ticket_module.process_request, req) self.assertEqual([], req.chrome['warnings']) self.assertEqual(comment, ticket.get_change(1)['fields']['comment']['new']) req = make_req('user2') self.assertTrue(self.ticket_module.match_request(req)) self.ticket_module.process_request(req) self.assertEqual(1, len(req.chrome['warnings'])) self.assertEqual("No permissions to add a comment.", unicode(req.chrome['warnings'][0])) def test_change_milestone_requires_milestone_view(self): """Changing ticket milestone requires MILESTONE_VIEW.""" perm_sys = PermissionSystem(self.env) self._insert_ticket(summary='the summary') for name in ('milestone1', 'milestone2'): m = Milestone(self.env) m.name = name m.insert() def make_req(authname): return MockRequest(self.env, authname=authname, method='GET', path_info='/ticket/1') def get_milestone_field(fields): for field in fields: if 'milestone' == field['name']: return field perm_sys.grant_permission('user', 'TICKET_VIEW') req = make_req('user') self.assertTrue(self.ticket_module.match_request(req)) data = self.ticket_module.process_request(req)[1] milestone_field = get_milestone_field(data['fields']) self.assertFalse(milestone_field['editable']) self.assertEqual([], milestone_field['optgroups'][0]['options']) self.assertEqual([], milestone_field['optgroups'][1]['options']) perm_sys.grant_permission('user_w_mv', 'TICKET_VIEW') perm_sys.grant_permission('user_w_mv', 'MILESTONE_VIEW') req = make_req('user_w_mv') self.assertTrue(self.ticket_module.match_request(req)) data = self.ticket_module.process_request(req)[1] milestone_field = get_milestone_field(data['fields']) self.assertTrue(milestone_field['editable']) self.assertEqual([], milestone_field['optgroups'][0]['options']) self.assertEqual(['milestone1', 'milestone2'], milestone_field['optgroups'][1]['options']) def test_newticket_has_auto_preview(self): """New ticket page has autopreview.""" req = MockRequest(self.env, method='GET', path_info='/newticket') self.assertTrue(self.ticket_module.process_request(req)) self.ticket_module.process_request(req) self.assertTrue(self._has_auto_preview(req)) def test_newticket_autopreview_disabled_when_no_workflow_actions(self): """Newticket autopreview disabled when no workflow actions.""" config = self.env.config config.remove('ticket-workflow', 'create') config.remove('ticket-workflow', 'create_and_assign') req = MockRequest(self.env, method='GET', path_info='/newticket') self.assertTrue(self.ticket_module.process_request(req)) data = self.ticket_module.process_request(req)[1] self.assertEqual([], data['action_controls']) self.assertFalse(self._has_auto_preview(req)) self.assertTrue(data['disable_submit']) def test_ticket_autopreview_disabled_when_no_workflow_actions(self): """Ticket autopreview disabled when no workflow actions.""" config = self.env.config for option in config.options('ticket-workflow'): if not option[0].startswith('leave'): config.remove('ticket-workflow', option[0]) req = MockRequest(self.env, method='GET', path_info='/ticket/1') self.assertTrue(self.ticket_module.process_request(req)) data = self.ticket_module.process_request(req)[1] self.assertEqual([], data['action_controls']) self.assertFalse(self._has_auto_preview(req)) self.assertTrue(data['disable_submit'])
class TicketModuleTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() self.ticket_module = TicketModule(self.env) def _insert_ticket(self, **kw): """Helper for inserting a ticket into the database""" ticket = Ticket(self.env) for k, v in kw.items(): ticket[k] = v return ticket.insert() def test_ticket_module_as_default_handler(self): """The New Ticket mainnav entry is active when TicketModule is the `default_handler` and navigating to the base url. Test for regression of http://trac.edgewall.org/ticket/8791. """ req = MockRequest(self.env) chrome = Chrome(self.env).prepare_request(req, self.ticket_module) name = None for item in chrome['nav']['mainnav']: if item['active'] is True: name = item['name'] break self.assertEqual('newticket', name) def _test_invalid_cnum_raises(self, action, cnum=None): self._insert_ticket() req = MockRequest(self.env, args={'action': action, 'id': '1'}) if cnum is not None: req.args.update({'cnum': cnum}) self.assertRaises(TracError, self.ticket_module.process_request, req) def test_comment_history_cnum_missing_raises(self): self._test_invalid_cnum_raises('comment-history') def test_comment_history_cnum_invalid_type_raises(self): self._test_invalid_cnum_raises('comment-history', 'a') def test_comment_history_cnum_empty_raises(self): self._test_invalid_cnum_raises('comment-history', '') def test_comment_history_cnum_out_of_range(self): """Out of range cnum returns an empty history.""" self._insert_ticket() req = MockRequest(self.env, args={ 'action': 'comment-history', 'id': '1', 'cnum': '1' }) resp = self.ticket_module.process_request(req) self.assertEqual([], resp[1]['history']) def test_comment_diff_cnum_missing_raises(self): self._test_invalid_cnum_raises('comment-diff') def test_comment_diff_cnum_invalid_type_raises(self): self._test_invalid_cnum_raises('comment-diff', 'a') def test_comment_diff_cnum_empty_raises(self): self._test_invalid_cnum_raises('comment-diff', '') def test_comment_diff_cnum_out_of_range_raises(self): self._insert_ticket() req = MockRequest(self.env, args={ 'action': 'comment-diff', 'id': '1', 'cnum': '1' }) self.assertRaises(ResourceNotFound, self.ticket_module.process_request, req) def _test_newticket_with_enum_as_custom_field(self, field_name): self.env.config.set('ticket-custom', field_name, 'text') self.env.config.set('ticket-custom', '%s.label' % field_name, '(%s)' % field_name) with self.env.db_transaction as db: if field_name in ('milestone', 'component', 'version'): db("DELETE FROM %s" % field_name) elif field_name == 'type': db("DELETE FROM enum WHERE type='ticket_type'") else: db("DELETE FROM enum WHERE type=%s", (field_name, )) tktsys = TicketSystem(self.env) tktsys.reset_ticket_fields() del tktsys.custom_fields req = MockRequest(self.env, path_info='/newticket') self.assertEqual(True, self.ticket_module.match_request(req)) resp = self.ticket_module.process_request(req) for field in resp[1]['fields']: if field['name'] == field_name: self.assertEqual('(%s)' % field_name, field['label']) self.assertTrue(field['custom']) self.assertFalse(field['options']) self.assertFalse(field.get('optgroups')) break else: self.fail('Missing %s in fields' % field_name) def test_newticket_with_component_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('component') def test_newticket_with_milestone_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('milestone') def test_newticket_with_priority_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('priority') def test_newticket_with_resolution_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('resolution') def test_newticket_with_severity_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('severity') def test_newticket_with_type_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('type') def test_newticket_with_version_as_custom_field(self): self._test_newticket_with_enum_as_custom_field('version')