def process_request(self, req): """Creating a new pull request ticket needs some pre- and post- processing: * Check if the given repository is a fork of another known repository. * Initialize some ticket fields. Then forward the processing to `TicketModule` * Add the repository as is must be looked up via the used path and is not yet known to the ticket. """ req.perm.require('TICKET_CREATE') rm = RepositoryManager(self.env) reponame, repo, path = rm.get_repository_by_path(req.args.get('path')) convert_managed_repository(self.env, repo) if not repo.is_fork: raise TracError(_("Repository is not a fork.")) req.args['type'] = 'pull request' req.args['pr_srcrev'] = req.args.get('pr_srcrev', repo.get_youngest_rev()) tm = TicketModule(self.env) template, data, content_type = tm.process_request(req) data.update({'pr_srcrepo': repo}) return template, data, content_type
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 TicketModuleTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() self.ticket_module = TicketModule(self.env) def tearDown(self): self.env.reset_db() def _create_request(self, authname='anonymous', **kwargs): kw = { 'path_info': '/', 'perm': MockPerm(), 'args': _RequestArgs(), 'href': self.env.href, 'abs_href': self.env.abs_href, 'tz': utc, 'locale': None, 'lc_time': locale_en, 'session': DetachedSession(self.env, authname), 'authname': authname, 'chrome': { 'notices': [], 'warnings': [] }, 'method': None, 'get_header': lambda v: None, 'is_xhr': False, 'form_token': None } if 'args' in kwargs: kw['args'].update(kwargs.pop('args')) kw.update(kwargs) def redirect(url, permanent=False): raise RequestDone return Mock(add_redirect_listener=lambda x: [].append(x), redirect=redirect, **kw) 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 = self._create_request() 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_known_users([('user1', 'User One', None), ('user2', 'User Two', None)]) tkt_id = self._insert_ticket(reporter='user1', owner='user2') req = self._create_request(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 = self._create_request(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_known_users([('author', 'The Author', '*****@*****.**')]) tkt = self._create_ticket_with_change({}, {'comment': 'the comment'}, 'author') req = self._create_request(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 = self._create_request(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 = self._create_request(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 = self._create_request(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 = self._create_request(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 = self._create_request(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 = self._create_request(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 = self._create_request(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 = self._create_request(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 = self._create_request(args={ 'action': 'comment-diff', 'id': '1', 'cnum': '1' }) self.assertRaises(ResourceNotFound, self.ticket_module.process_request, req)
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')
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_request(self, authname='anonymous', **kwargs): kw = {'path_info': '/', 'perm': MockPerm(), 'args': _RequestArgs(), 'href': self.env.href, 'abs_href': self.env.abs_href, 'tz': utc, 'locale': None, 'lc_time': locale_en, 'session': {}, 'authname': authname, 'chrome': {'notices': [], 'warnings': []}, 'method': None, 'get_header': lambda v: None, 'is_xhr': False, 'form_token': None} if 'args' in kwargs: kw['args'].update(kwargs.pop('args')) kw.update(kwargs) def redirect(url, permanent=False): raise RequestDone return Mock(add_redirect_listener=lambda x: [].append(x), redirect=redirect, **kw) def _create_ticket_with_change(self, old_props, new_props): """Create a ticket with `old_props` and apply properties in `new_props`. """ t = Ticket(self.env) t.populate(old_props) t.insert() t.populate(new_props) t.save_changes('actor') 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 = self._create_request() 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_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 = self._create_request(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 = self._create_request(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 = self._create_request(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 = self._create_request(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 = self._create_request(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 = self._create_request(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 = self._create_request(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 = self._create_request(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 = self._create_request(args={'action': 'comment-diff', 'id': '1', 'cnum': '1'}) self.assertRaises(ResourceNotFound, self.ticket_module.process_request, req)