class SetOwnerAttributeTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True) self.perm_sys = PermissionSystem(self.env) self.ctlr = TicketSystem(self.env).action_controllers[0] self.ticket = insert_ticket(self.env, status='new') self.env.insert_users([ (user, None, None) for user in ('user1', 'user2', 'user3', 'user4') ]) permissions = [('user1', 'TICKET_EDIT_CC'), ('user2', 'TICKET_EDIT_CC'), ('user2', 'TICKET_BATCH_MODIFY'), ('user3', 'TICKET_ADMIN'), ('user4', 'TICKET_VIEW'), ('user1', 'group1'), ('user2', 'group1'), ('user2', 'group2'), ('user3', 'group2'), ('user4', 'group3')] for perm in permissions: self.perm_sys.grant_permission(*perm) self.req = MockRequest(self.env, authname='user1') self.expected = """\ to <select id="action_reassign_reassign_owner" \ name="action_reassign_reassign_owner"><option selected="selected" \ value="user1">user1</option><option value="user2">user2</option>\ <option value="user3">user3</option></select>""" def _reload_workflow(self): self.ctlr.actions = self.ctlr.get_all_actions() def tearDown(self): self.env.reset_db() def test_users(self): self.env.config.set('ticket-workflow', 'reassign.set_owner', 'user1, user2, user3') self._reload_workflow() args = self.req, self.ticket, 'reassign' label, control, hints = self.ctlr.render_ticket_action_control(*args) self.assertEqual(self.expected, str(control)) def test_groups(self): self.env.config.set('ticket-workflow', 'reassign.set_owner', 'group1, group2') self._reload_workflow() args = self.req, self.ticket, 'reassign' label, control, hints = self.ctlr.render_ticket_action_control(*args) self.assertEqual(self.expected, str(control)) def test_permission(self): self.env.config.set('ticket-workflow', 'reassign.set_owner', 'TICKET_EDIT_CC, TICKET_BATCH_MODIFY') self._reload_workflow() args = self.req, self.ticket, 'reassign' label, control, hints = self.ctlr.render_ticket_action_control(*args) self.assertEqual(self.expected, str(control))
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 RequestDispatcherTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(path=mkdtemp()) os.mkdir(self.env.templates_dir) filepath = os.path.join(self.env.templates_dir, TestStubRequestHandler.filename) create_file(filepath, TestStubRequestHandler.template) self.filename = os.path.join(self.env.path, 'test.txt') self.data = 'contents\n' create_file(self.filename, self.data, 'wb') def tearDown(self): self.env.reset_db_and_disk() def _insert_session(self): sid = '1234567890abcdef' name = 'First Last' email = '*****@*****.**' self.env.insert_users([(sid, name, email, 0)]) return sid, name, email def _content(self): yield 'line1,' yield 'line2,' yield 'line3\n' def test_invalid_default_date_format_raises_exception(self): self.env.config.set('trac', 'default_date_format', u'ĭšo8601') self.assertEqual(u'ĭšo8601', self.env.config.get('trac', 'default_date_format')) self.assertRaises(ConfigurationError, getattr, RequestDispatcher(self.env), 'default_date_format') def test_get_session_returns_session(self): """Session is returned when database is reachable.""" sid, name, email = self._insert_session() req = MockRequest(self.env, path_info='/test-stub', cookie='trac_session=%s;' % sid) request_dispatcher = RequestDispatcher(self.env) request_dispatcher.set_default_callbacks(req) self.assertRaises(RequestDone, request_dispatcher.dispatch, req) self.assertIsInstance(req.session, Session) self.assertEqual(sid, req.session.sid) self.assertEqual(name, req.session['name']) self.assertEqual(email, req.session['email']) self.assertFalse(req.session.authenticated) self.assertEqual('200 Ok', req.status_sent[0]) self.assertIn('<h1>Hello World</h1>', req.response_sent.getvalue()) def test_get_session_returns_fake_session(self): """Fake session is returned when database is not reachable.""" sid = self._insert_session()[0] request_dispatcher = RequestDispatcher(self.env) def get_session(req): """Simulates an unreachable database.""" _get_connector = DatabaseManager.get_connector def get_connector(self): raise TracError("Database not reachable") DatabaseManager.get_connector = get_connector DatabaseManager(self.env).shutdown() session = request_dispatcher._get_session(req) DatabaseManager.get_connector = _get_connector return session req = MockRequest(self.env, path_info='/test-stub', cookie='trac_session=%s;' % sid) req.callbacks['session'] = get_session self.assertRaises(RequestDone, request_dispatcher.dispatch, req) self.assertIn(('DEBUG', "Chosen handler is <Component trac.web.tests" ".main.TestStubRequestHandler>"), self.env.log_messages) self.assertIn(('ERROR', "can't retrieve session: TracError: Database " "not reachable"), self.env.log_messages) self.assertIsInstance(req.session, FakeSession) self.assertIsNone(req.session.sid) self.assertNotIn('name', req.session) self.assertNotIn('email', req.session) self.assertFalse(req.session.authenticated) self.assertEqual('200 Ok', req.status_sent[0]) self.assertIn('<h1>Hello World</h1>', req.response_sent.getvalue()) def test_invalid_session_id_returns_fake_session(self): """Fake session is returned when session id is invalid.""" sid = 'a' * 23 + '$' # last char invalid, sid must be alphanumeric. req = MockRequest(self.env, path_info='/test-stub', cookie='trac_session=%s;' % sid) request_dispatcher = RequestDispatcher(self.env) request_dispatcher.set_default_callbacks(req) with self.assertRaises(RequestDone): request_dispatcher.dispatch(req) self.assertIn(('DEBUG', "Chosen handler is <Component trac.web.tests" ".main.TestStubRequestHandler>"), self.env.log_messages) self.assertIn(('WARNING', "can't retrieve session: " "Session ID must be alphanumeric."), self.env.log_messages) self.assertIsInstance(req.session, FakeSession) self.assertIsNone(req.session.sid) self.assertEqual('200 Ok', req.status_sent[0]) self.assertIn('<h1>Hello World</h1>', req.response_sent.getvalue()) def test_set_valid_xsendfile_header(self): """Send file using xsendfile header.""" self.env.config.set('trac', 'use_xsendfile', True) self.env.config.set('trac', 'xsendfile_header', 'X-Accel-Redirect') req = MockRequest(self.env) request_dispatcher = RequestDispatcher(self.env) request_dispatcher.set_default_callbacks(req) # File is sent using xsendfile. self.assertRaises(RequestDone, req.send_file, self.filename) self.assertEqual(['200 Ok'], req.status_sent) self.assertEqual('text/plain', req.headers_sent['Content-Type']) self.assertEqual(self.filename, req.headers_sent['X-Accel-Redirect']) self.assertNotIn('X-Sendfile', req.headers_sent) self.assertIsNone(req._response) self.assertEqual('', req.response_sent.getvalue()) def _test_file_not_sent_using_xsendfile_header(self, xsendfile_header): req = MockRequest(self.env) request_dispatcher = RequestDispatcher(self.env) request_dispatcher.set_default_callbacks(req) # File is not sent using xsendfile. self.assertRaises(RequestDone, req.send_file, self.filename) self.assertEqual(['200 Ok'], req.status_sent) self.assertEqual('text/plain', req.headers_sent['Content-Type']) self.assertNotIn(xsendfile_header, req.headers_sent) self.assertEqual('_FileWrapper', type(req._response).__name__) self.assertEqual('', req.response_sent.getvalue()) def test_set_invalid_xsendfile_header(self): """Not sent by xsendfile header because header is invalid.""" xsendfile_header = '(X-SendFile)' self.env.config.set('trac', 'use_xsendfile', True) self.env.config.set('trac', 'xsendfile_header', xsendfile_header) self._test_file_not_sent_using_xsendfile_header(xsendfile_header) def test_xsendfile_is_disabled(self): """Not sent by xsendfile header because xsendfile is disabled.""" xsendfile_header = 'X-SendFile' self.env.config.set('trac', 'use_xsendfile', False) self.env.config.set('trac', 'xsendfile_header', xsendfile_header) self._test_file_not_sent_using_xsendfile_header(xsendfile_header) def _test_configurable_headers(self, method): # Reserved headers not allowed. content_type = 'not-allowed' self.env.config.set('http-headers', 'Content-Type', content_type) # Control code not allowed. custom1 = '\x00custom1' self.env.config.set('http-headers', 'X-Custom-1', custom1) # Many special characters allowed in header name. custom2 = 'Custom2-!#$%&\'*+.^_`|~' self.env.config.set('http-headers', custom2, 'custom2') # Some special characters not allowed in header name. self.env.config.set('http-headers', 'X-Custom-(3)', 'custom3') req = MockRequest(self.env, method='POST') request_dispatcher = RequestDispatcher(self.env) request_dispatcher.set_default_callbacks(req) self.assertRaises(RequestDone, method, req) self.assertNotEqual('not-allowed', req.headers_sent.get('Content-Type')) self.assertNotIn('x-custom-1', req.headers_sent) self.assertIn(custom2.lower(), req.headers_sent) self.assertNotIn('x-custom-(3)', req.headers_sent) self.assertIn( ('WARNING', "[http-headers] invalid headers are ignored: " "u'content-type': u'not-allowed', " "u'x-custom-1': u'\\x00custom1', " "u'x-custom-(3)': u'custom3'"), self.env.log_messages) def test_send_configurable_headers(self): def send(req): req.send(self._content()) self._test_configurable_headers(send) def test_send_error_configurable_headers(self): def send_error(req): req.send_error(None, self._content()) self._test_configurable_headers(send_error) def test_send_configurable_headers_no_override(self): """Headers in request not overridden by configurable headers.""" self.env.config.set('http-headers', 'X-XSS-Protection', '1; mode=block') request_dispatcher = RequestDispatcher(self.env) req1 = MockRequest(self.env) request_dispatcher.set_default_callbacks(req1) self.assertRaises(RequestDone, req1.send, self._content()) self.assertNotIn('X-XSS-protection', req1.headers_sent) self.assertIn('x-xss-protection', req1.headers_sent) self.assertEqual('1; mode=block', req1.headers_sent['x-xss-protection']) req2 = MockRequest(self.env, method='POST') request_dispatcher.set_default_callbacks(req2) self.assertRaises(RequestDone, req2.send, self._content()) self.assertNotIn('x-xss-protection', req2.headers_sent) self.assertIn('X-XSS-Protection', req2.headers_sent) self.assertEqual('0', req2.headers_sent['X-XSS-Protection'])
class KnownUsersTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() users = [('123', None, '*****@*****.**', 0), ('jane', 'Jane', None, 1), ('joe', None, '*****@*****.**', 1), ('tom', 'Tom', '*****@*****.**', 1)] self.env.insert_users(users) self.expected = [user[:3] for user in users if user[3] == 1] def tearDown(self): self.env.reset_db() def test_get_known_users_as_list_of_tuples(self): users = list(self.env.get_known_users()) i = 0 for i, user in enumerate(users): self.assertEqual(self.expected[i], user) else: self.assertEqual(2, i) def test_get_known_users_as_dict(self): users = self.env.get_known_users(as_dict=True) self.assertEqual(3, len(users)) for exp in self.expected: self.assertEqual(exp[1:], users[exp[0]]) def test_get_known_users_is_cached(self): self.env.get_known_users() self.env.get_known_users(as_dict=True) self.env.insert_users([('user4', None, None)]) users_list = list(self.env.get_known_users()) users_dict = self.env.get_known_users(as_dict=True) i = 0 for i, user in enumerate(users_list): self.assertEqual(self.expected[i], user) self.assertIn(self.expected[i][0], users_dict) else: self.assertEqual(2, i) self.assertEqual(3, len(users_dict)) def test_invalidate_known_users_cache(self): self.env.get_known_users() self.env.get_known_users(as_dict=True) user = ('user4', 'User Four', '*****@*****.**') self.env.insert_users([user]) self.expected.append(user[:3]) self.env.invalidate_known_users_cache() users_list = self.env.get_known_users() users_dict = self.env.get_known_users(as_dict=True) i = 0 for i, user in enumerate(users_list): self.assertEqual(self.expected[i], user) self.assertIn(self.expected[i][0], users_dict) else: self.assertEqual(3, i) self.assertEqual(4, len(users_dict))
class AdvancedPreferencePanelTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() def _insert_session(self): sid = '1234567890abcdef' name = 'First Last' email = '*****@*****.**' self.env.insert_users([(sid, name, email, 0)]) return sid, name, email def test_change_session_key(self): """Change session key.""" old_sid, name, email = self._insert_session() new_sid = 'a' * 24 req = MockRequest(self.env, method='POST', path_info='/prefs/advanced', cookie='trac_session=%s;' % old_sid, args={'newsid': new_sid}) module = PreferencesModule(self.env) self.assertEqual(old_sid, req.session.sid) self.assertEqual(name, req.session.get('name')) self.assertEqual(email, req.session.get('email')) self.assertTrue(module.match_request(req)) with self.assertRaises(RequestDone): module.process_request(req) self.assertIn("Your preferences have been saved.", req.chrome['notices']) self.assertEqual(new_sid, req.session.sid) self.assertEqual(name, req.session.get('name')) self.assertEqual(email, req.session.get('email')) def test_change_to_invalid_session_key_raises_exception(self): """Changing session key with invalid session key raises TracValueError. """ req = MockRequest(self.env, method='POST', path_info='/prefs/advanced', args={'newsid': 'a' * 23 + '$'}) module = PreferencesModule(self.env) self.assertTrue(module.match_request(req)) with self.assertRaises(TracValueError) as cm: module.process_request(req) self.assertEqual("Session ID must be alphanumeric.", unicode(cm.exception)) def test_load_session_key(self): """Load session key.""" old_sid = 'a' * 24 new_sid, name, email = self._insert_session() req = MockRequest(self.env, method='POST', path_info='/prefs/advanced', cookie='trac_session=%s;' % old_sid, args={ 'loadsid': new_sid, 'restore': True }) module = PreferencesModule(self.env) self.assertEqual(old_sid, req.session.sid) self.assertIsNone(req.session.get('name')) self.assertIsNone(req.session.get('email')) self.assertTrue(module.match_request(req)) module.process_request(req) self.assertIn("The session has been loaded.", req.chrome['notices']) self.assertEqual(new_sid, req.session.sid) self.assertEqual(name, req.session.get('name')) self.assertEqual(email, req.session.get('email')) def test_load_invalid_session_key_raises_exception(self): """Loading session key with invalid session key raises TracValueError. """ req = MockRequest(self.env, method='POST', path_info='/prefs/advanced', args={ 'loadsid': 'a' * 23 + '$', 'restore': True }) module = PreferencesModule(self.env) self.assertTrue(module.match_request(req)) with self.assertRaises(TracValueError) as cm: module.process_request(req) self.assertEqual("Session ID must be alphanumeric.", unicode(cm.exception))
class RequestDispatcherTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(path=mkdtemp()) os.mkdir(self.env.templates_dir) filepath = os.path.join(self.env.templates_dir, TestStubRequestHandler.filename) create_file(filepath, TestStubRequestHandler.template) def tearDown(self): self.env.reset_db_and_disk() def _insert_session(self): sid = '1234567890abcdef' name = 'First Last' email = '*****@*****.**' self.env.insert_users([(sid, name, email, 0)]) return sid, name, email def test_invalid_default_date_format_raises_exception(self): self.env.config.set('trac', 'default_date_format', u'ĭšo8601') self.assertEqual(u'ĭšo8601', self.env.config.get('trac', 'default_date_format')) self.assertRaises(ConfigurationError, getattr, RequestDispatcher(self.env), 'default_date_format') def test_get_session_returns_session(self): """Session is returned when database is reachable.""" sid, name, email = self._insert_session() req = MockRequest(self.env, path_info='/test-stub', cookie='trac_session=%s;' % sid) request_dispatcher = RequestDispatcher(self.env) self.assertRaises(RequestDone, request_dispatcher.dispatch, req) self.assertIsInstance(req.session, Session) self.assertEqual(sid, req.session.sid) self.assertEqual(name, req.session['name']) self.assertEqual(email, req.session['email']) self.assertFalse(req.session.authenticated) self.assertEqual('200 Ok', req.status_sent[0]) self.assertIn('<h1>Hello World</h1>', req.response_sent.getvalue()) def test_get_session_returns_fake_session(self): """Fake session is returned when database is not reachable.""" sid = self._insert_session()[0] _get_session = RequestDispatcher._get_session def get_session(self, req): """Simulates an unreachable database.""" _get_connector = DatabaseManager.get_connector def get_connector(self): raise TracError("Database not reachable") DatabaseManager.get_connector = get_connector DatabaseManager(self.env).shutdown() session = _get_session(self, req) DatabaseManager.get_connector = _get_connector return session req = MockRequest(self.env, path_info='/test-stub', cookie='trac_session=%s;' % sid) request_dispatcher = RequestDispatcher(self.env) request_dispatcher._get_session = \ types.MethodType(get_session, request_dispatcher) self.assertRaises(RequestDone, request_dispatcher.dispatch, req) self.assertIn(('DEBUG', "Chosen handler is <Component trac.web.tests" ".main.TestStubRequestHandler>"), self.env.log_messages) self.assertIn(('ERROR', "can't retrieve session: TracError: Database " "not reachable"), self.env.log_messages) self.assertIsInstance(req.session, FakeSession) self.assertIsNone(req.session.sid) self.assertNotIn('name', req.session) self.assertNotIn('email', req.session) self.assertFalse(req.session.authenticated) self.assertEqual('200 Ok', req.status_sent[0]) self.assertIn('<h1>Hello World</h1>', req.response_sent.getvalue())
class RestrictOwnerTestCase(unittest.TestCase): def setUp(self): tmpdir = os.path.realpath(tempfile.gettempdir()) self.env = EnvironmentStub(enable=['trac.*', AuthzPolicy], path=tmpdir) self.env.config.set('trac', 'permission_policies', 'AuthzPolicy, DefaultPermissionPolicy') self.env.config.set('ticket', 'restrict_owner', True) self.perm_sys = PermissionSystem(self.env) self.env.insert_users([('user1', 'User C', '*****@*****.**'), ('user2', 'User A', '*****@*****.**'), ('user3', 'User D', '*****@*****.**'), ('user4', 'User B', '*****@*****.**')]) self.perm_sys.grant_permission('user1', 'TICKET_MODIFY') self.perm_sys.grant_permission('user2', 'TICKET_VIEW') self.perm_sys.grant_permission('user3', 'TICKET_MODIFY') self.perm_sys.grant_permission('user4', 'TICKET_MODIFY') self.authz_file = os.path.join(tmpdir, 'trac-authz-policy') create_file(self.authz_file) self.env.config.set('authz_policy', 'authz_file', self.authz_file) self.ctlr = TicketSystem(self.env).action_controllers[0] self.req1 = MockRequest(self.env, authname='user1') self.ticket = insert_ticket(self.env, status='new') def tearDown(self): self.env.reset_db() os.remove(self.authz_file) def _reload_workflow(self): self.ctlr.actions = self.ctlr.get_all_actions() def test_set_owner(self): """Restricted owners list contains users with TICKET_MODIFY. """ self.env.config.set('trac', 'show_full_names', False) ctrl = self.ctlr.render_ticket_action_control(self.req1, self.ticket, 'reassign') self.assertEqual('reassign', ctrl[0]) self.assertIn('value="user1">user1</option>', str(ctrl[1])) self.assertNotIn('value="user2">user2</option>', str(ctrl[1])) self.assertIn('value="user3">user3</option>', str(ctrl[1])) self.assertIn('value="user4">user4</option>', str(ctrl[1])) def test_set_owner_fine_grained_permissions(self): """Fine-grained permission checks when populating the restricted owners list (#10833). """ self.env.config.set('trac', 'show_full_names', False) create_file(self.authz_file, textwrap.dedent("""\ [ticket:1] user4 = !TICKET_MODIFY """)) ctrl = self.ctlr.render_ticket_action_control(self.req1, self.ticket, 'reassign') self.assertEqual('reassign', ctrl[0]) self.assertIn('value="user1">user1</option>', str(ctrl[1])) self.assertNotIn('value="user2">user2</option>', str(ctrl[1])) self.assertIn('value="user3">user3</option>', str(ctrl[1])) self.assertNotIn('value="user4">user4</option>', str(ctrl[1])) def test_set_owner_show_fullnames(self): """Full names are sorted when [trac] show_full_names = True.""" ctrl = self.ctlr.render_ticket_action_control(self.req1, self.ticket, 'reassign') self.assertEqual('reassign', ctrl[0]) self.assertEqual("""\ to <select id="action_reassign_reassign_owner"\ name="action_reassign_reassign_owner">\ <option value="user4">User B</option>\ <option selected="selected" value="user1">User C</option>\ <option value="user3">User D</option></select>\ """, str(ctrl[1]))
class SessionTestCase(unittest.TestCase): """Unit tests for the persistent session support.""" request_handlers = [] @classmethod def setUpClass(cls): class DefaultHandlerStub(Component): implements(IRequestHandler) def match_request(self, req): pass def process_request(req): pass cls.request_handlers = [DefaultHandlerStub] @classmethod def tearDownClass(cls): for component in cls.request_handlers: ComponentMeta.deregister(component) def setUp(self): self.env = EnvironmentStub() def tearDown(self): self.env.reset_db() def test_new_session(self): """ Verify that a session cookie gets sent back to the client for a new session. """ req = MockRequest(self.env, authname='anonymous') session = Session(self.env, req) self.assertEqual(session.sid, req.outcookie['trac_session'].value) self.assertEqual( 0, self.env.db_query("SELECT COUNT(*) FROM session")[0][0]) def test_anonymous_session(self): """ Verify that session variables are stored in the database. """ req = MockRequest(self.env, authname='anonymous') req.incookie['trac_session'] = '123456' session = Session(self.env, req) self.assertEqual('123456', session.sid) self.assertNotIn('trac_session', req.outcookie) def _test_authenticated_session(self, username): """ Verifies that a session cookie does not get used if the user is logged in, and that Trac expires the cookie. """ req = MockRequest(self.env, authname=username) req.incookie['trac_session'] = '123456' session = Session(self.env, req) self.assertEqual(username, session.sid) session['foo'] = 'bar' session.save() self.assertEqual(0, req.outcookie['trac_session']['expires']) def test_authenticated_session(self): self._test_authenticated_session('john') self._test_authenticated_session('j.smith') self._test_authenticated_session(u'Jöhn') # non-ascii username self._test_authenticated_session('*****@*****.**') # LDAP username def _test_session_promotion(self, username): """ Verifies that an existing anonymous session gets promoted to an authenticated session when the user logs in. """ with self.env.db_transaction as db: db("INSERT INTO session VALUES ('123456', 0, 0)") req = MockRequest(self.env, authname=username) req.incookie['trac_session'] = '123456' session = Session(self.env, req) self.assertEqual(username, session.sid) session.save() self.assertEqual([(username, 1)], self.env.db_query( """SELECT sid, authenticated FROM session WHERE sid=%s""", (username, ))) def test_session_promotion(self): self._test_session_promotion('john') self._test_session_promotion('j.smith') self._test_session_promotion(u'Jöhn') # non-ascii username self._test_session_promotion('*****@*****.**') # LDAP username sessions = self.env.db_query("SELECT sid, authenticated FROM session") self.assertEqual( {('john', 1), ('j.smith', 1), (u'Jöhn', 1), ('*****@*****.**', 1)}, set(sessions)) def _test_new_session_promotion(self, username): """ Verifies that even without a preexisting anonymous session, an authenticated session will be created when the user logs in. (same test as above without the initial INSERT) """ with self.env.db_transaction: req = MockRequest(self.env, authname=username) req.incookie['trac_session'] = '123456' session = Session(self.env, req) self.assertEqual(username, session.sid) session.save() self.assertEqual([(username, 1)], self.env.db_query( """SELECT sid, authenticated FROM session WHERE sid=%s""", (username, ))) def test_new_session_promotion(self): self._test_new_session_promotion('john') self._test_new_session_promotion('j.smith') self._test_new_session_promotion(u'Jöhn') # non-ascii username self._test_new_session_promotion('*****@*****.**') # LDAP username sessions = self.env.db_query("SELECT sid, authenticated FROM session") self.assertEqual( {('john', 1), ('j.smith', 1), (u'Jöhn', 1), ('*****@*****.**', 1)}, set(sessions)) def test_as_int(self): with self.env.db_transaction as db: db("INSERT INTO session VALUES ('123456', 1, 0)") db.executemany( """ INSERT INTO session_attribute VALUES (%s,%s,%s,%s) """, (('123456', 1, 'foo', 'bar'), ('123456', 1, 'baz', 3))) req = MockRequest(self.env, authname='123456') session = Session(self.env, req) self.assertEqual('bar', session.get('foo')) self.assertEqual(2, session.as_int('foo', 2)) self.assertEqual(3, session.as_int('baz', 1)) self.assertEqual(2, session.as_int('baz', 1, max=2)) self.assertEqual(4, session.as_int('baz', 1, min=4)) self.assertIsNone(session.as_int('bat')) def test_as_float(self): with self.env.db_transaction as db: db("INSERT INTO session VALUES ('123456', 1, 0)") db.executemany( """ INSERT INTO session_attribute VALUES (%s,%s,%s,%s) """, (('123456', 1, 'foo', 'bar'), ('123456', 1, 'baz', 3.3))) req = MockRequest(self.env, authname='123456') session = Session(self.env, req) self.assertEqual('bar', session.get('foo')) self.assertEqual(2, session.as_float('foo', 2)) self.assertEqual(3.3, session.as_float('baz', 1)) self.assertEqual(2.2, session.as_float('baz', 1, max=2.2)) self.assertEqual(4.4, session.as_float('baz', 1, min=4.4)) self.assertIsNone(session.as_float('bat')) def test_as_bool(self): with self.env.db_transaction as db: db("INSERT INTO session VALUES ('123456', 1, 0)") db.executemany( """ INSERT INTO session_attribute VALUES (%s,%s,%s,%s) """, (('123456', 1, 'foo', 'bar'), ('123456', 1, 'baz', 1))) req = MockRequest(self.env, authname='123456') session = Session(self.env, req) self.assertEqual('bar', session.get('foo')) self.assertIsNone(session.as_bool('foo', None)) self.assertTrue(session.as_int('baz')) self.assertTrue(session.as_int('baz', False)) self.assertIsNone(session.as_bool('bat')) def test_add_anonymous_session_var(self): """ Verify that new variables are inserted into the 'session' table in the database for an anonymous session. """ req = MockRequest(self.env, authname='anonymous') req.incookie['trac_session'] = '123456' session = Session(self.env, req) session['foo'] = 'bar' session.save() self.assertEqual( 'bar', self.env.db_query( "SELECT value FROM session_attribute WHERE sid='123456'")[0] [0]) def test_modify_anonymous_session_var(self): """ Verify that modifying an existing variable updates the 'session' table accordingly for an anonymous session. """ with self.env.db_transaction as db: db("INSERT INTO session VALUES ('123456', 0, 0)") db(""" INSERT INTO session_attribute VALUES ('123456', 0, 'foo', 'bar') """) req = MockRequest(self.env, authname='anonymous') req.incookie['trac_session'] = '123456' session = Session(self.env, req) self.assertEqual('bar', session['foo']) session['foo'] = 'baz' session.save() self.assertEqual( 'baz', self.env.db_query( "SELECT value FROM session_attribute WHERE sid='123456'")[0] [0]) def test_delete_anonymous_session_var(self): """ Verify that modifying a variable updates the 'session' table accordingly for an anonymous session. """ with self.env.db_transaction as db: db("INSERT INTO session VALUES ('123456', 0, 0)") db(""" INSERT INTO session_attribute VALUES ('123456', 0, 'foo', 'bar') """) req = MockRequest(self.env, authname='anonymous') req.incookie['trac_session'] = '123456' session = Session(self.env, req) self.assertEqual('bar', session['foo']) del session['foo'] session.save() self.assertEqual( 0, self.env.db_query(""" SELECT COUNT(*) FROM session_attribute WHERE sid='123456' AND name='foo' """)[0][0]) def _purge_anonymous_session(self): now = int(time_now()) lifetime = 90 * 86400 # default lifetime with self.env.db_transaction as db: db.executemany("INSERT INTO session VALUES (%s, 0, %s)", [('123456', 0), ('987654', now - lifetime - 3600), ('876543', now - lifetime + 3600), ('765432', now - 3600)]) db.executemany( """ INSERT INTO session_attribute VALUES (%s, 0, 'foo', 'bar') """, [('987654', ), ('876543', ), ('765432', )]) with self.env.db_transaction as db: # We need to modify a different session to trigger the purging req = MockRequest(self.env, authname='anonymous') req.incookie['trac_session'] = '123456' session = Session(self.env, req) session['foo'] = 'bar' session.save() return [ row[0] for row in self.env.db_query(""" SELECT sid FROM session WHERE authenticated=0 ORDER BY sid """) ] def test_purge_anonymous_session(self): """ Verify that old sessions get purged. """ sids = self._purge_anonymous_session() self.assertEqual(['123456', '765432', '876543'], sids) def test_purge_anonymous_session_with_short_lifetime(self): self.env.config.set('trac', 'anonymous_session_lifetime', '1') sids = self._purge_anonymous_session() self.assertEqual(['123456', '765432'], sids) def test_purge_anonymous_session_disabled(self): self.env.config.set('trac', 'anonymous_session_lifetime', '0') sids = self._purge_anonymous_session() self.assertEqual(['123456', '765432', '876543', '987654'], sids) def test_delete_empty_session(self): """ Verify that a session gets deleted when it doesn't have any data except for the 'last_visit' timestamp. """ now = time_now() # Make sure the session has data so that it doesn't get dropped with self.env.db_transaction as db: db("INSERT INTO session VALUES ('123456', 0, %s)", (int(now - UPDATE_INTERVAL - 3600), )) db(""" INSERT INTO session_attribute VALUES ('123456', 0, 'foo', 'bar') """) req = MockRequest(self.env, authname='anonymous') req.incookie['trac_session'] = '123456' session = Session(self.env, req) del session['foo'] session.save() self.assertEqual( 0, self.env.db_query(""" SELECT COUNT(*) FROM session WHERE sid='123456' AND authenticated=0 """)[0][0]) def test_change_anonymous_session(self): """ Verify that changing from one anonymous session to an inexisting anonymous session creates the new session and doesn't carry over variables from the previous session. """ with self.env.db_transaction as db: db("INSERT INTO session VALUES ('123456', 0, 0)") db(""" INSERT INTO session_attribute VALUES ('123456', 0, 'foo', 'bar') """) req = MockRequest(self.env, authname='anonymous') req.incookie['trac_session'] = '123456' session = Session(self.env, req) self.assertEqual({'foo': 'bar'}, session) session.get_session('7890') session['baz'] = 'moo' session.save() self.assertEqual({'baz': 'moo'}, session) with self.env.db_query as db: self.assertEqual( 1, db(""" SELECT COUNT(*) FROM session WHERE sid='7890' AND authenticated=0 """)[0][0]) self.assertEqual([('baz', 'moo')], db(""" SELECT name, value FROM session_attribute WHERE sid='7890' AND authenticated=0 """)) def test_add_authenticated_session_var(self): """ Verify that new variables are inserted into the 'session' table in the database for an authenticated session. """ req = MockRequest(self.env, authname='john') session = Session(self.env, req) session['foo'] = 'bar' session.save() self.assertEqual( 'bar', self.env.db_query(""" SELECT value FROM session_attribute WHERE sid='john' AND name='foo' """)[0][0]) def test_modify_authenticated_session_var(self): """ Verify that modifying an existing variable updates the 'session' table accordingly for an authenticated session. """ with self.env.db_transaction as db: db("INSERT INTO session VALUES ('john', 1, 0)") db("INSERT INTO session_attribute VALUES ('john',1,'foo','bar')") req = MockRequest(self.env, authname='john') session = Session(self.env, req) self.assertEqual('bar', session['foo']) session['foo'] = 'baz' session.save() self.assertEqual( 'baz', self.env.db_query(""" SELECT value FROM session_attribute WHERE sid='john' AND name='foo' """)[0][0]) def test_authenticated_session_independence_var(self): """ Verify that an anonymous session with the same name as an authenticated session doesn't interfere with the latter. """ with self.env.db_transaction as db: db("INSERT INTO session VALUES ('john', 1, 0)") db("INSERT INTO session_attribute VALUES ('john',1,'foo','bar')") self.assertEqual( 'bar', self.env.db_query(""" SELECT value FROM session_attribute WHERE sid='john' AND authenticated=1 AND name='foo' """)[0][0]) req = MockRequest(self.env, authname='anonymous') req.incookie['trac_session'] = 'john' session = Session(self.env, req) self.assertNotIn('foo', session) session['foo'] = 'baz' session.save() rows = self.env.db_query(""" SELECT value FROM session_attribute WHERE sid='john' AND authenticated=1 AND name='foo' """) self.assertEqual(1, len(rows)) self.assertEqual('bar', rows[0][0]) rows = self.env.db_query(""" SELECT value FROM session_attribute WHERE sid='john' AND authenticated=0 AND name='foo' """) self.assertEqual(1, len(rows)) self.assertEqual('baz', rows[0][0]) def test_delete_authenticated_session_var(self): """ Verify that deleting a variable updates the 'session' table accordingly for an authenticated session. """ with self.env.db_transaction as db: db("INSERT INTO session VALUES ('john', 1, 0)") db("INSERT INTO session_attribute VALUES ('john', 1, 'foo', 'bar')" ) req = MockRequest(self.env, authname='john') session = Session(self.env, req) self.assertEqual('bar', session['foo']) del session['foo'] session.save() self.assertEqual( 0, self.env.db_query(""" SELECT COUNT(*) FROM session_attribute WHERE sid='john' AND name='foo' """)[0][0]) def test_update_session(self): """ Verify that accessing a session after one day updates the sessions 'last_visit' variable so that the session doesn't get purged. """ now = time_now() # Make sure the session has data so that it doesn't get dropped with self.env.db_transaction as db: db("INSERT INTO session VALUES ('123456', 0, 1)") db(""" INSERT INTO session_attribute VALUES ('123456', 0, 'foo', 'bar') """) req = MockRequest(self.env, authname='anonymous') req.incookie['trac_session'] = '123456' session = Session(self.env, req) session['modified'] = True session.save() # updating does require modifications self.assertEqual(PURGE_AGE, req.outcookie['trac_session']['expires']) self.assertAlmostEqual( now, int( self.env.db_query(""" SELECT last_visit FROM session WHERE sid='123456' AND authenticated=0 """)[0][0]), -1) def test_modify_detached_session(self): """ Verify that modifying a variable in a session not associated with a request updates the database accordingly. """ with self.env.db_transaction as db: db("INSERT INTO session VALUES ('john', 1, 0)") db("INSERT INTO session_attribute VALUES ('john', 1, 'foo', 'bar')" ) session = DetachedSession(self.env, 'john') self.assertEqual('bar', session['foo']) session['foo'] = 'baz' session.save() self.assertEqual( 'baz', self.env.db_query(""" SELECT value FROM session_attribute WHERE sid='john' AND name='foo' """)[0][0]) def test_delete_detached_session_var(self): """ Verify that removing a variable in a session not associated with a request deletes the variable from the database. """ with self.env.db_transaction as db: db("INSERT INTO session VALUES ('john', 1, 0)") db("INSERT INTO session_attribute VALUES ('john', 1, 'foo', 'bar')" ) session = DetachedSession(self.env, 'john') self.assertEqual('bar', session['foo']) del session['foo'] session.save() self.assertEqual( 0, self.env.db_query(""" SELECT COUNT(*) FROM session_attribute WHERE sid='john' AND name='foo' """)[0][0]) def test_session_set(self): """Verify that setting a variable in a session to the default value removes it from the session. """ with self.env.db_transaction as db: db("INSERT INTO session VALUES ('john', 1, 0)") db("INSERT INTO session_attribute VALUES ('john', 1, 'foo', 'bar')" ) session = DetachedSession(self.env, 'john') self.assertEqual('bar', session['foo']) # Setting the variable to the default value removes the variable with self.env.db_transaction as db: session.set('foo', 'default', 'default') session.save() self.assertEqual( 0, self.env.db_query(""" SELECT COUNT(*) FROM session_attribute WHERE sid='john' AND name='foo' """)[0][0]) # Setting the variable to a value different from the default sets it with self.env.db_transaction as db: session.set('foo', 'something', 'default') session.save() self.assertEqual( 'something', self.env.db_query(""" SELECT value FROM session_attribute WHERE sid='john' AND name='foo' """)[0][0]) def test_create_new_session(self): """Setting attribute on a new session invalidates known_users cache.""" sid = 'user' session = DetachedSession(self.env, sid) session.authenticated = True self.assertEqual([], list(self.env.get_known_users())) session.save() known_users = list(self.env.get_known_users()) self.assertEqual(1, len(known_users)) self.assertEqual(sid, known_users[0][0]) self.assertIsNone(known_users[0][1]) self.assertIsNone(known_users[0][2]) def test_create_new_anonymous_session(self): """Setting attribute on a new anonymous session doesn't invalidate known_users cache.""" sid = 'anonymous' session = DetachedSession(self.env, sid) session.authenticated = False self.assertEqual([], list(self.env.get_known_users())) # insert a session record without invalidating cache self.env.insert_users([('user', 'Name' '*****@*****.**', 1)]) session.save() self.assertEqual([], list(self.env.get_known_users())) self.env.invalidate_known_users_cache() self.assertEqual(1, len(list(self.env.get_known_users()))) def test_session_set_email(self): """Setting session email invalidates known_users cache.""" sid = 'user' email = '*****@*****.**' session = DetachedSession(self.env, sid) session['email'] = email self.assertEqual([], list(self.env.get_known_users())) session.save() known_users = list(self.env.get_known_users()) self.assertEqual(1, len(known_users)) self.assertEqual(sid, known_users[0][0]) self.assertIsNone(known_users[0][1]) self.assertEqual(email, known_users[0][2]) def test_session_set_name(self): """Setting session name invalidates known_users cache.""" sid = 'user' name = 'The User' session = DetachedSession(self.env, sid) session['name'] = name self.assertEqual([], list(self.env.get_known_users())) session.save() known_users = list(self.env.get_known_users()) self.assertEqual(1, len(known_users)) self.assertEqual(sid, known_users[0][0]) self.assertEqual(name, known_users[0][1]) self.assertIsNone(known_users[0][2]) def test_session_admin_list(self): auth_list, anon_list, all_list = _prep_session_table(self.env) sess_admin = SessionAdmin(self.env) # Verify the empty case self.assertRaises(StopIteration, next, sess_admin._get_list([])) self.assertEqual([i for i in sess_admin._get_list(['authenticated'])], auth_list) self.assertEqual([i for i in sess_admin._get_list(['anonymous'])], anon_list) self.assertEqual([i for i in sess_admin._get_list(['*'])], all_list) self.assertEqual([i for i in sess_admin._get_list(['name00'])][0], auth_list[0]) self.assertEqual([i for i in sess_admin._get_list(['name10:0'])][0], anon_list[0]) self.assertEqual( [i for i in sess_admin._get_list(['name00', 'name01', 'name02'])], all_list[:3]) def test_session_admin_add(self): _prep_session_table(self.env) sess_admin = SessionAdmin(self.env) self.assertRaises(Exception, sess_admin._do_add, 'name00') sess_admin._do_add('john') self.assertEqual({}, get_session_attrs(self.env, 'john')) self.assertIn(('john', None, None), list(self.env.get_known_users())) sess_admin._do_add('john1', 'John1') self.assertEqual({'name': 'John1'}, get_session_attrs(self.env, 'john1')) self.assertIn(('john1', 'John1', None), list(self.env.get_known_users())) sess_admin._do_add('john2', 'John2', '*****@*****.**') self.assertEqual({ 'name': 'John2', 'email': '*****@*****.**' }, get_session_attrs(self.env, 'john2')) self.assertIn(('john2', 'John2', '*****@*****.**'), list(self.env.get_known_users())) sess_admin._do_add('alice1', None, '*****@*****.**') self.assertEqual({'email': '*****@*****.**'}, get_session_attrs(self.env, 'alice1')) sess_admin._do_add('alice2', '', '*****@*****.**') self.assertEqual({'email': '*****@*****.**'}, get_session_attrs(self.env, 'alice2')) sess_admin._do_add('bob1', 'Bob 1', None) self.assertEqual({'name': 'Bob 1'}, get_session_attrs(self.env, 'bob1')) sess_admin._do_add('bob2', 'Bob 2', '') self.assertEqual({'name': 'Bob 2'}, get_session_attrs(self.env, 'bob2')) sess_admin._do_add('charlie1', '', '') self.assertEqual({}, get_session_attrs(self.env, 'charlie1')) sess_admin._do_add('charlie2', None, None) self.assertEqual({}, get_session_attrs(self.env, 'charlie2')) def test_session_admin_set(self): _prep_session_table(self.env) sess_admin = SessionAdmin(self.env) self.assertRaises(TracError, sess_admin._do_set, 'name', 'nothere', 'foo') self.env.get_known_users() # Prep the cache self.assertIn(('name00', 'val00', 'val00'), list(self.env.get_known_users())) sess_admin._do_set('name', 'name00', 'john') self.assertEqual({ 'name': 'john', 'email': 'val00' }, get_session_attrs(self.env, 'name00')) self.assertIn(('name00', 'john', 'val00'), list(self.env.get_known_users())) sess_admin._do_set('email', 'name00', '*****@*****.**') self.assertEqual({ 'name': 'john', 'email': '*****@*****.**' }, get_session_attrs(self.env, 'name00')) self.assertIn(('name00', 'john', '*****@*****.**'), list(self.env.get_known_users())) sess_admin._do_set('default_handler', 'name00', 'DefaultHandlerStub') self.assertEqual( { 'name': 'john', 'email': '*****@*****.**', 'default_handler': 'DefaultHandlerStub' }, get_session_attrs(self.env, 'name00')) sess_admin._do_set('name', 'name00', '') self.assertEqual( { 'email': '*****@*****.**', 'default_handler': 'DefaultHandlerStub' }, get_session_attrs(self.env, 'name00')) sess_admin._do_set('email', 'name00', '') self.assertEqual({'default_handler': 'DefaultHandlerStub'}, get_session_attrs(self.env, 'name00')) sess_admin._do_set('default_handler', 'name00', '') self.assertEqual({}, get_session_attrs(self.env, 'name00')) def test_session_admin_delete(self): _prep_session_table(self.env) sess_admin = SessionAdmin(self.env) self.assertIn(('name00', 'val00', 'val00'), list(self.env.get_known_users())) sess_admin._do_delete('name00') self.assertIsNone(get_session_attrs(self.env, 'name00')) self.assertNotIn(('name00', 'val00', 'val00'), list(self.env.get_known_users())) sess_admin._do_delete('nothere') self.assertIsNone(get_session_attrs(self.env, 'nothere')) auth_list, anon_list, all_list = _prep_session_table(self.env) sess_admin._do_delete('anonymous') result = [i for i in sess_admin._get_list(['*'])] self.assertEqual(result, auth_list) def test_session_admin_purge(self): sess_admin = SessionAdmin(self.env) auth_list, anon_list, all_list = \ _prep_session_table(self.env, spread_visits=True) sess_admin._do_purge('2010-01-02') result = [i for i in sess_admin._get_list(['*'])] self.assertEqual(result, auth_list + anon_list) self.assertEqual({ 'name': 'val10', 'email': 'val10' }, get_session_attrs(self.env, anon_list[0][0])) self.assertEqual({ 'name': 'val11', 'email': 'val11' }, get_session_attrs(self.env, anon_list[1][0])) auth_list, anon_list, all_list = \ _prep_session_table(self.env, spread_visits=True) sess_admin._do_purge('2010-01-12') result = [i for i in sess_admin._get_list(['*'])] self.assertEqual(result, auth_list + anon_list[1:]) rows = self.env.db_query( """ SELECT name, value FROM session_attribute WHERE sid = %s """, (anon_list[0][0], )) self.assertEqual([], rows) self.assertIsNone(get_session_attrs(self.env, anon_list[0][0])) self.assertEqual({ 'name': 'val11', 'email': 'val11' }, get_session_attrs(self.env, anon_list[1][0])) def test_session_get_session_with_invalid_sid(self): req = MockRequest(self.env, authname='anonymous') session = Session(self.env, req) session.get_session('0123456789') self.assertEqual('0123456789', session.sid) session.get_session('abcxyz') self.assertEqual('abcxyz', session.sid) session.get_session('abc123xyz') self.assertEqual('abc123xyz', session.sid) self.assertRaises(TracError, session.get_session, 'abc 123 xyz') self.assertRaises(TracError, session.get_session, 'abc-123-xyz') self.assertRaises(TracError, session.get_session, 'abc<i>123</i>xyz') self.assertRaises(TracError, session.get_session, u'abc123xÿz') self.assertRaises(TracError, session.get_session, u'abc¹₂³xyz') # Unicode digits def test_session_change_id_with_invalid_sid(self): req = MockRequest(self.env, authname='anonymous', base_path='/') session = Session(self.env, req) session.change_sid('0123456789') self.assertEqual('0123456789', session.sid) session.change_sid('abcxyz') self.assertEqual('abcxyz', session.sid) session.change_sid('abc123xyz') self.assertEqual('abc123xyz', session.sid) self.assertRaises(TracError, session.change_sid, 'abc 123 xyz') self.assertRaises(TracError, session.change_sid, 'abc-123-xyz') self.assertRaises(TracError, session.change_sid, 'abc<i>123</i>xyz') self.assertRaises(TracError, session.change_sid, u'abc123xÿz') self.assertRaises(TracError, session.change_sid, u'abc¹₂³xyz') # Unicode digits
class ConfigurableTicketWorkflowTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() config = self.env.config config.set('ticket-workflow', 'change_owner', 'new -> new') config.set('ticket-workflow', 'change_owner.operations', 'set_owner') self.ctlr = TicketSystem(self.env).action_controllers[0] self.ticket_module = TicketModule(self.env) def tearDown(self): self.env.reset_db() def _add_component(self, name='test', owner='owner1'): component = Component(self.env) component.name = name component.owner = owner component.insert() def _reload_workflow(self): self.ctlr.actions = self.ctlr.get_all_actions() def test_get_all_actions_custom_attribute(self): """Custom attribute in ticket-workflow.""" config = self.env.config['ticket-workflow'] config.set('resolve.set_milestone', 'reject') all_actions = self.ctlr.get_all_actions() resolve_action = None for name, attrs in all_actions.items(): if name == 'resolve': resolve_action = attrs self.assertIsNotNone(resolve_action) self.assertIn('set_milestone', list(resolve_action)) self.assertEqual('reject', resolve_action['set_milestone']) def test_owner_from_component(self): """Verify that the owner of a new ticket is set to the owner of the component. """ self._add_component('component3', 'cowner3') req = MockRequest(self.env, method='POST', args={ 'field_reporter': 'reporter1', 'field_summary': 'the summary', 'field_component': 'component3', }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, 1) self.assertEqual('component3', ticket['component']) self.assertEqual('cowner3', ticket['owner']) def test_component_change(self): """New ticket owner is updated when the component is changed. """ self._add_component('component3', 'cowner3') self._add_component('component4', 'cowner4') ticket = insert_ticket(self.env, reporter='reporter1', summary='the summary', component='component3', owner='cowner3', status='new') req = MockRequest(self.env, method='POST', args={ 'id': ticket.id, 'field_component': 'component4', 'submit': True, 'action': 'leave', 'view_time': str(to_utimestamp(ticket['changetime'])), }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, ticket.id) self.assertEqual('component4', ticket['component']) self.assertEqual('cowner4', ticket['owner']) def test_component_change_and_owner_change(self): """New ticket owner is not updated if owner is explicitly changed. """ self._add_component('component3', 'cowner3') self._add_component('component4', 'cowner4') ticket = insert_ticket(self.env, reporter='reporter1', summary='the summary', component='component3', status='new') req = MockRequest(self.env, method='POST', args={ 'id': ticket.id, 'field_component': 'component4', 'submit': True, 'action': 'change_owner', 'action_change_owner_reassign_owner': 'owner1', 'view_time': str(to_utimestamp(ticket['changetime'])), }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, ticket.id) self.assertEqual('component4', ticket['component']) self.assertEqual('owner1', ticket['owner']) def test_old_owner_not_old_component_owner(self): """New ticket owner is not updated if old owner is not the owner of the old component. """ self._add_component('component3', 'cowner3') self._add_component('component4', 'cowner4') ticket = insert_ticket(self.env, reporter='reporter1', summary='the summary', component='component3', owner='owner1', status='new') req = MockRequest(self.env, method='POST', args={ 'id': ticket.id, 'field_component': 'component4', 'submit': True, 'action': 'leave', 'view_time': str(to_utimestamp(ticket['changetime'])), }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, ticket.id) self.assertEqual('component4', ticket['component']) self.assertEqual('owner1', ticket['owner']) def test_new_component_has_no_owner(self): """Ticket is not disowned when the component is changed to a component with no owner. """ self._add_component('component3', 'cowner3') self._add_component('component4', '') ticket = insert_ticket(self.env, reporter='reporter1', summary='the summary', component='component3', owner='cowner3', status='new') req = MockRequest(self.env, method='POST', args={ 'id': ticket.id, 'field_component': 'component4', 'submit': True, 'action': 'leave', 'view_time': str(to_utimestamp(ticket['changetime'])), }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, ticket.id) self.assertEqual('component4', ticket['component']) self.assertEqual('cowner3', ticket['owner']) def _test_get_allowed_owners(self): ticket = insert_ticket(self.env, summary='Ticket 1') self.env.insert_users([('user1', None, None, 1), ('user2', None, None, 1), ('user3', None, None, 1)]) ps = PermissionSystem(self.env) for user in ('user1', 'user3'): ps.grant_permission(user, 'TICKET_MODIFY') self.env.config.set('ticket', 'restrict_owner', True) return ticket def test_get_allowed_owners_returns_set_owner_list(self): """Users specified in `set_owner` for the action are returned.""" req = None action = {'set_owner': ['user4', 'user5']} ticket = self._test_get_allowed_owners() self.assertEqual(['user4', 'user5'], self.ctlr.get_allowed_owners(req, ticket, action)) def test_get_allowed_owners_returns_user_with_ticket_modify(self): """Users with TICKET_MODIFY are are returned if `set_owner` is not specified for the action. """ req = None action = {} ticket = self._test_get_allowed_owners() self.assertEqual(['user1', 'user3'], self.ctlr.get_allowed_owners(req, ticket, action)) def test_transition_to_star(self): """Workflow hint is not be added in a workflow transition to *, for example: <none> -> * AdvancedTicketWorkflow uses the behavior for the triage operation (see #12823) """ config = self.env.config config.set('ticket-workflow', 'create_and_triage', '<none> -> *') config.set('ticket-workflow', 'create_and_triage.operations', 'triage') self._reload_workflow() ticket = Ticket(self.env) req = MockRequest(self.env, path_info='/newticket', method='POST') label, control, hints = \ self.ctlr.render_ticket_action_control(req, ticket, 'create_and_triage') self.assertEqual('create and triage', label) self.assertEqual('', unicode(control)) self.assertEqual('', unicode(hints)) def test_get_actions_by_operation_for_req(self): """Request with no permission checking.""" req = MockRequest(self.env, path_info='/ticket/1') ticket = insert_ticket(self.env, status='new') actions = self.ctlr.get_actions_by_operation_for_req(req, ticket, 'set_owner') self.assertEqual([(0, u'change_owner'), (0, u'reassign')], actions) def test_get_actions_by_operation_for_req_with_ticket_modify(self): """User without TICKET_MODIFY won't have reassign action.""" req = MockRequest(self.env, authname='user1', path_info='/ticket/1') ticket = insert_ticket(self.env, status='new') actions = self.ctlr.get_actions_by_operation_for_req(req, ticket, 'set_owner') self.assertEqual([(0, u'change_owner')], actions) def test_get_actions_by_operation_for_req_without_ticket_modify(self): """User with TICKET_MODIFY will have reassign action.""" PermissionSystem(self.env).grant_permission('user1', 'TICKET_MODIFY') req = MockRequest(self.env, authname='user1', path_info='/ticket/1') ticket = insert_ticket(self.env, status='new') actions = self.ctlr.get_actions_by_operation_for_req(req, ticket, 'set_owner') self.assertEqual([(0, u'change_owner'), (0, u'reassign')], actions) def test_ignores_other_operations(self): """Ignores operations not defined by ConfigurableTicketWorkflow. """ self.env.config.set('ticket-workflow', 'review', 'assigned -> review') self.env.config.set('ticket-workflow', 'review.operations', 'CodeReview') ctw = ConfigurableTicketWorkflow(self.env) ticket = Ticket(self.env) ticket.populate({'summary': '#13013', 'status': 'assigned'}) ticket.insert() req = MockRequest(self.env) self.assertNotIn((0, 'review'), ctw.get_ticket_actions(req, ticket))
class RequestDispatcherTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(path=tempfile.mkdtemp( prefix='trac-tempenv-')) os.mkdir(self.env.templates_dir) filepath = os.path.join(self.env.templates_dir, TestStubRequestHandler.filename) create_file(filepath, TestStubRequestHandler.template) def tearDown(self): self.env.reset_db_and_disk() def _insert_session(self): sid = '1234567890abcdef' name = 'First Last' email = '*****@*****.**' self.env.insert_users([(sid, name, email, 0)]) return sid, name, email def test_invalid_default_date_format_raises_exception(self): self.env.config.set('trac', 'default_date_format', u'ĭšo8601') self.assertEqual(u'ĭšo8601', self.env.config.get('trac', 'default_date_format')) self.assertRaises(ConfigurationError, getattr, RequestDispatcher(self.env), 'default_date_format') def test_get_session_returns_session(self): """Session is returned when database is reachable.""" sid, name, email = self._insert_session() req = MockRequest(self.env, path_info='/test-stub', cookie='trac_session=%s;' % sid) request_dispatcher = RequestDispatcher(self.env) request_dispatcher.set_default_callbacks(req) self.assertRaises(RequestDone, request_dispatcher.dispatch, req) self.assertIsInstance(req.session, Session) self.assertEqual(sid, req.session.sid) self.assertEqual(name, req.session['name']) self.assertEqual(email, req.session['email']) self.assertFalse(req.session.authenticated) self.assertEqual('200 Ok', req.status_sent[0]) self.assertIn('<h1>Hello World</h1>', req.response_sent.getvalue()) def test_get_session_returns_fake_session(self): """Fake session is returned when database is not reachable.""" sid = self._insert_session()[0] request_dispatcher = RequestDispatcher(self.env) def get_session(req): """Simulates an unreachable database.""" _get_connector = DatabaseManager.get_connector def get_connector(self): raise TracError("Database not reachable") DatabaseManager.get_connector = get_connector DatabaseManager(self.env).shutdown() session = request_dispatcher._get_session(req) DatabaseManager.get_connector = _get_connector return session req = MockRequest(self.env, path_info='/test-stub', cookie='trac_session=%s;' % sid) req.callbacks['session'] = get_session self.assertRaises(RequestDone, request_dispatcher.dispatch, req) self.assertIsInstance(req.session, FakeSession) self.assertIsNone(req.session.sid) self.assertNotIn('name', req.session) self.assertNotIn('email', req.session) self.assertFalse(req.session.authenticated) self.assertEqual('200 Ok', req.status_sent[0]) self.assertIn('<h1>Hello World</h1>', req.response_sent.getvalue()) def test_invalid_session_id_returns_fake_session(self): """Fake session is returned when session id is invalid.""" sid = 'a' * 23 + '$' # last char invalid, sid must be alphanumeric. req = MockRequest(self.env, path_info='/test-stub', cookie='trac_session=%s;' % sid) request_dispatcher = RequestDispatcher(self.env) request_dispatcher.set_default_callbacks(req) self.assertRaises(RequestDone, request_dispatcher.dispatch, req) self.assertIsInstance(req.session, FakeSession) self.assertIsNone(req.session.sid) self.assertEqual('200 Ok', req.status_sent[0]) self.assertIn('<h1>Hello World</h1>', req.response_sent.getvalue())
class CrashDumpWebUiTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=['trac.*', 'crashdump.*']) self.env.path = tempfile.mkdtemp() self.db_mgr = DatabaseManager(self.env) self.env.upgrade() #self.db = self.env.get_db_cnx() self.crashdump_module = CrashDumpModule(self.env) def tearDown(self): #self.db.close() self.env.shutdown() shutil.rmtree(self.env.path) 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 ticket.insert() with self.env.db_transaction as db: links = CrashDumpTicketLinks(self.env, ticket, db=db) if 'linked_crashes' in kw: links.crashes = kw['linked_crashes'] links.save(author='anonymous', db=db) db.commit() return ticket, links def _insert_crashdump(self, **kw): """Helper for inserting a ticket into the database""" crash = CrashDump(env=self.env) for k, v in kw.items(): crash[k] = v crash.insert() return crash def test_no_crash_id(self): req = MockRequest(self.env, authname='user', method='GET', args={'without-crashid':'42'}) self.assertRaises(ResourceNotFound, self.crashdump_module.process_request, req) def test_non_existing_crash_id(self): req = MockRequest(self.env, authname='user', method='GET', args={'crashid':'42'}) self.assertRaises(ResourceNotFound, self.crashdump_module.process_request, req) def test_action_view_crash(self): """Full name of reporter and owner are used in ticket properties.""" self.env.insert_users([('user1', 'User One', ''), ('user2', 'User Two', '')]) crash = self._insert_crashdump(reporter='user1', owner='user2') req = MockRequest(self.env, authname='user', method='GET', args={'crashid':crash.id, 'action': 'view'}) tmpl, data, extra = self.crashdump_module.process_request(req) self.assertEqual(tmpl, 'report.html') def test_action_view_crash_child(self): """Full name of reporter and owner are used in ticket properties.""" self.env.insert_users([('user1', 'User One', ''), ('user2', 'User Two', '')]) crash = self._insert_crashdump(reporter='user1', owner='user2') for param in ['sysinfo', 'sysinfo_ex', 'fast_protect_version_info', 'exception', 'memory_regions', 'modules', 'threads', 'memory_block', 'stackdump']: req = MockRequest(self.env, authname='user', method='GET', args={'crashid':crash.id, 'action': 'view', 'params': [param] }) tmpl, data, extra = self.crashdump_module.process_request(req) self.assertEqual(tmpl, param + '.html') def test_action_view_ticket_linked_crash(self): """Full name of reporter and owner are used in ticket properties.""" self.env.insert_users([('user1', 'User One', ''), ('user2', 'User Two', '')]) crash = self._insert_crashdump(reporter='user1', owner='user2') tkt, tkt_links = self._insert_ticket(reporter='user1', owner='user2', linked_crashes='%i' % crash.id) req = MockRequest(self.env, authname='user', method='GET', args={'crashid':crash.id, 'action': 'view'}) tmpl, data, extra = self.crashdump_module.process_request(req) self.assertEqual(tmpl, 'report.html') self.assertEqual(crash.linked_tickets, [tkt.id]) def test_action_view_ticket_linked_crash_bad_crashid(self): """Full name of reporter and owner are used in ticket properties.""" self.env.insert_users([('user1', 'User One', ''), ('user2', 'User Two', '')]) tkt, tkt_links = self._insert_ticket(reporter='user1', owner='user2') crash = self._insert_crashdump(reporter='user1', owner='user2', linked_crashes='Bad#%i' % tkt.id) req = MockRequest(self.env, authname='user', method='GET', args={'crashid':crash.id, 'action': 'view'}) tmpl, data, extra = self.crashdump_module.process_request(req) self.assertEqual(tmpl, 'report.html') self.assertEqual(crash.linked_tickets, [])
class TicketSystemTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True) self.perm = PermissionSystem(self.env) self.ticket_system = TicketSystem(self.env) self.req = MockRequest(self.env) def tearDown(self): self.env.reset_db() def _get_actions(self, ticket_dict): ts = TicketSystem(self.env) ticket = Ticket(self.env) ticket.populate(ticket_dict) id = ticket.insert() return ts.get_available_actions(self.req, Ticket(self.env, id)) def _get_ticket_field(self, field_name): fields = TicketSystem(self.env).get_ticket_fields() return next((i for i in fields if i['name'] == field_name)) def test_custom_field_text(self): self.env.config.set('ticket-custom', 'test', 'text') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'text', 'label': 'Test', 'value': 'Foo bar', 'max_size': 0, 'order': 0, 'format': 'wiki', 'custom': True }, fields[0]) def test_custom_field_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', 'option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0, 'custom': True }, fields[0]) def test_custom_field_optional_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', '|option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0, 'optional': True, 'custom': True }, fields[0]) def test_custom_field_textarea(self): self.env.config.set('ticket-custom', 'test', 'textarea') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.rows', '4') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'textarea', 'label': 'Test', 'value': 'Foo bar', 'height': 4, 'order': 0, 'max_size': 0, 'format': 'wiki', 'custom': True }, fields[0]) def test_custom_field_time(self): self.env.config.set('ticket-custom', 'test', 'time') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual( { 'name': 'test', 'type': 'time', 'label': 'Test', 'value': '', 'order': 0, 'format': 'datetime', 'custom': True }, fields[0]) def test_custom_field_order(self): self.env.config.set('ticket-custom', 'test1', 'text') self.env.config.set('ticket-custom', 'test1.order', '2') self.env.config.set('ticket-custom', 'test2', 'text') self.env.config.set('ticket-custom', 'test2.order', '1') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual('test2', fields[0]['name']) self.assertEqual('test1', fields[1]['name']) def test_custom_field_label(self): self.env.config.set('ticket-custom', '_test_one', 'text') self.env.config.set('ticket-custom', 'test_two', 'text') self.env.config.set('ticket-custom', 'test_two.label', 'test_2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual('Test one', fields[0]['label']) self.assertEqual('test_2', fields[1]['label']) def test_available_actions_full_perms(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.perm.grant_permission('anonymous', 'TICKET_MODIFY') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'new'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_no_perms(self): self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'})) def test_available_actions_create_only(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_chgprop_only(self): # CHGPROP is not enough for changing a ticket's state (#3289) self.perm.grant_permission('anonymous', 'TICKET_CHGPROP') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'})) def test_get_allowed_owners_restrict_owner_false(self): self.env.config.set('ticket', 'restrict_owner', False) self.assertIsNone(self.ticket_system.get_allowed_owners()) def test_get_allowed_owners_restrict_owner_true(self): self.env.config.set('ticket', 'restrict_owner', True) self.env.insert_users([('user3', None, None), ('user1', None, None)]) self.perm.grant_permission('user4', 'TICKET_MODIFY') self.perm.grant_permission('user3', 'TICKET_MODIFY') self.perm.grant_permission('user2', 'TICKET_VIEW') self.perm.grant_permission('user1', 'TICKET_MODIFY') self.assertEqual(['user1', 'user3'], self.ticket_system.get_allowed_owners()) def test_get_ticket_fields_version_rename(self): """Cached ticket fields are updated when version is renamed.""" fields = self.ticket_system.get_ticket_fields() version_field = self._get_ticket_field('version') v2 = Version(self.env, '2.0') v2.name = '0.0' v2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_version_field = self._get_ticket_field('version') self.assertNotEqual(fields, updated_fields) self.assertEqual(['2.0', '1.0'], version_field['options']) self.assertEqual(['1.0', '0.0'], updated_version_field['options']) def test_get_ticket_fields_version_update_time(self): """Cached ticket fields are updated when version release time is changed. """ fields = self.ticket_system.get_ticket_fields() version_field = self._get_ticket_field('version') v1 = Version(self.env, '1.0') v1.time = datetime_now(utc) v2 = Version(self.env, '2.0') v2.time = v1.time - timedelta(seconds=1) v1.update() v2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_version_field = self._get_ticket_field('version') self.assertNotEqual(fields, updated_fields) self.assertEqual(['2.0', '1.0'], version_field['options']) self.assertEqual(['1.0', '2.0'], updated_version_field['options']) def test_get_ticket_fields_milestone_rename(self): """Cached ticket fields are updated when milestone is renamed.""" fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.name = 'milestone5' m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual( ['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual( ['milestone1', 'milestone3', 'milestone4', 'milestone5'], updated_milestone_field['options']) def test_get_ticket_fields_milestone_update_completed(self): """Cached ticket fields are updated when milestone is completed date is changed. """ fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.completed = datetime_now(utc) m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual( ['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual( ['milestone2', 'milestone1', 'milestone3', 'milestone4'], updated_milestone_field['options']) def test_get_ticket_fields_milestone_update_due(self): """Cached ticket fields are updated when milestone due date is changed. """ fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.due = datetime_now(utc) m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual( ['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual( ['milestone2', 'milestone1', 'milestone3', 'milestone4'], updated_milestone_field['options']) def test_resource_exists_valid_resource_id(self): Ticket(self.env).insert() r1 = Resource('ticket', 1) r2 = Resource('ticket', 2) self.assertTrue(self.ticket_system.resource_exists(r1)) self.assertFalse(self.ticket_system.resource_exists(r2)) def test_resource_exists_invalid_resource_id(self): """Exception is trapped from resource with invalid id.""" r1 = Resource('ticket', None) r2 = Resource('ticket', 'abc') r3 = Resource('ticket', '2.') r4 = Resource('ticket', r2) self.assertFalse(self.ticket_system.resource_exists(r1)) self.assertFalse(self.ticket_system.resource_exists(r2)) self.assertFalse(self.ticket_system.resource_exists(r3)) self.assertFalse(self.ticket_system.resource_exists(r4))
class ConfigurableTicketWorkflowTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub() config = self.env.config config.set('ticket-workflow', 'change_owner', 'new -> new') config.set('ticket-workflow', 'change_owner.operations', 'set_owner') self.ctlr = TicketSystem(self.env).action_controllers[0] self.ticket_module = TicketModule(self.env) def tearDown(self): self.env.reset_db() def _add_component(self, name='test', owner='owner1'): component = Component(self.env) component.name = name component.owner = owner component.insert() def _reload_workflow(self): self.ctlr.actions = self.ctlr.get_all_actions() def test_get_all_actions_custom_attribute(self): """Custom attribute in ticket-workflow.""" config = self.env.config['ticket-workflow'] config.set('resolve.set_milestone', 'reject') all_actions = self.ctlr.get_all_actions() resolve_action = None for name, attrs in all_actions.items(): if name == 'resolve': resolve_action = attrs self.assertIsNotNone(resolve_action) self.assertIn('set_milestone', list(resolve_action)) self.assertEqual('reject', resolve_action['set_milestone']) def test_owner_from_component(self): """Verify that the owner of a new ticket is set to the owner of the component. """ self._add_component('component3', 'cowner3') req = MockRequest(self.env, method='POST', args={ 'field_reporter': 'reporter1', 'field_summary': 'the summary', 'field_component': 'component3', }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, 1) self.assertEqual('component3', ticket['component']) self.assertEqual('cowner3', ticket['owner']) def test_component_change(self): """New ticket owner is updated when the component is changed. """ self._add_component('component3', 'cowner3') self._add_component('component4', 'cowner4') ticket = insert_ticket(self.env, reporter='reporter1', summary='the summary', component='component3', owner='cowner3', status='new') req = MockRequest(self.env, method='POST', args={ 'id': ticket.id, 'field_component': 'component4', 'submit': True, 'action': 'leave', 'view_time': str(to_utimestamp(ticket['changetime'])), }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, ticket.id) self.assertEqual('component4', ticket['component']) self.assertEqual('cowner4', ticket['owner']) def test_component_change_and_owner_change(self): """New ticket owner is not updated if owner is explicitly changed. """ self._add_component('component3', 'cowner3') self._add_component('component4', 'cowner4') ticket = insert_ticket(self.env, reporter='reporter1', summary='the summary', component='component3', status='new') req = MockRequest(self.env, method='POST', args={ 'id': ticket.id, 'field_component': 'component4', 'submit': True, 'action': 'change_owner', 'action_change_owner_reassign_owner': 'owner1', 'view_time': str(to_utimestamp(ticket['changetime'])), }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, ticket.id) self.assertEqual('component4', ticket['component']) self.assertEqual('owner1', ticket['owner']) def test_old_owner_not_old_component_owner(self): """New ticket owner is not updated if old owner is not the owner of the old component. """ self._add_component('component3', 'cowner3') self._add_component('component4', 'cowner4') ticket = insert_ticket(self.env, reporter='reporter1', summary='the summary', component='component3', owner='owner1', status='new') req = MockRequest(self.env, method='POST', args={ 'id': ticket.id, 'field_component': 'component4', 'submit': True, 'action': 'leave', 'view_time': str(to_utimestamp(ticket['changetime'])), }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, ticket.id) self.assertEqual('component4', ticket['component']) self.assertEqual('owner1', ticket['owner']) def test_new_component_has_no_owner(self): """Ticket is not disowned when the component is changed to a component with no owner. """ self._add_component('component3', 'cowner3') self._add_component('component4', '') ticket = insert_ticket(self.env, reporter='reporter1', summary='the summary', component='component3', owner='cowner3', status='new') req = MockRequest(self.env, method='POST', args={ 'id': ticket.id, 'field_component': 'component4', 'submit': True, 'action': 'leave', 'view_time': str(to_utimestamp(ticket['changetime'])), }) self.assertRaises(RequestDone, self.ticket_module.process_request, req) ticket = Ticket(self.env, ticket.id) self.assertEqual('component4', ticket['component']) self.assertEqual('cowner3', ticket['owner']) def _test_get_allowed_owners(self): ticket = insert_ticket(self.env, summary='Ticket 1') self.env.insert_users([('user1', None, None, 1), ('user2', None, None, 1), ('user3', None, None, 1)]) ps = PermissionSystem(self.env) for user in ('user1', 'user3'): ps.grant_permission(user, 'TICKET_MODIFY') self.env.config.set('ticket', 'restrict_owner', True) return ticket def test_get_allowed_owners_returns_set_owner_list(self): """Users specified in `set_owner` for the action are returned.""" req = None action = {'set_owner': ['user4', 'user5']} ticket = self._test_get_allowed_owners() self.assertEqual(['user4', 'user5'], self.ctlr.get_allowed_owners(req, ticket, action)) def test_get_allowed_owners_returns_user_with_ticket_modify(self): """Users with TICKET_MODIFY are are returned if `set_owner` is not specified for the action. """ req = None action = {} ticket = self._test_get_allowed_owners() self.assertEqual(['user1', 'user3'], self.ctlr.get_allowed_owners(req, ticket, action)) def test_status_change_with_operation(self): """Status change with operation.""" ticket = Ticket(self.env) ticket['new'] = 'status1' ticket['owner'] = 'user1' ticket.insert() req = MockRequest(self.env, path_info='/ticket', authname='user2', method='POST') label, control, hints = \ self.ctlr.render_ticket_action_control(req, ticket, 'accept') self.assertEqual('accept', label) self.assertEqual('', unicode(control)) self.assertEqual( "The owner will be <span class=\"trac-author-user\">" "user2</span>. The status will be 'accepted'.", unicode(hints)) def test_status_change_with_no_operation(self): """Existing ticket status change with no operation.""" config = self.env.config config.set('ticket-workflow', 'change_status', 'status1 -> status2') self._reload_workflow() ticket = Ticket(self.env) ticket['status'] = 'status1' ticket.insert() req = MockRequest(self.env, path_info='/ticket', method='POST') label, control, hints = \ self.ctlr.render_ticket_action_control(req, ticket, 'change_status') self.assertEqual('change status', label) self.assertEqual('', unicode(control)) self.assertEqual("Next status will be 'status2'.", unicode(hints)) def test_new_ticket_status_change_with_no_operation(self): """New ticket status change with no operation.""" config = self.env.config config.set('ticket-workflow', 'change_status', '<none> -> status1') self._reload_workflow() ticket = Ticket(self.env) req = MockRequest(self.env, path_info='/newticket', method='POST') label, control, hints = \ self.ctlr.render_ticket_action_control(req, ticket, 'change_status') self.assertEqual('change status', label) self.assertEqual('', unicode(control)) self.assertEqual("The status will be 'status1'.", unicode(hints)) def test_operation_with_no_status_change(self): """Operation with no status change.""" config = self.env.config config.set('ticket-workflow', 'change_owner', 'closed -> closed') config.set('ticket-workflow', 'change_owner.operations', 'set_owner') self._reload_workflow() ticket = Ticket(self.env) ticket['status'] = 'closed' ticket['owner'] = 'user2' ticket.insert() req = MockRequest(self.env, path_info='/ticket', method='POST', authname='user1') label, control, hints = \ self.ctlr.render_ticket_action_control(req, ticket, 'change_owner') self.assertEqual('change owner', label) self.assertEqual( 'to <input id="action_change_owner_reassign_owner" ' 'name="action_change_owner_reassign_owner" type="text" ' 'value="user1" />', unicode(control)) self.assertEqual( 'The owner will be changed from <span class="trac-author">' 'user2</span> to the specified user.', unicode(hints)) def test_transition_to_star(self): """Action not rendered by CTW for transition to * AdvancedTicketWorkflow uses the behavior for the triage operation (see #12823) """ config = self.env.config config.set('ticket-workflow', 'create_and_triage', '<none> -> *') config.set('ticket-workflow', 'create_and_triage.operations', 'triage') self._reload_workflow() ticket = Ticket(self.env) req = MockRequest(self.env, path_info='/newticket', method='POST') actions = self.ctlr.get_ticket_actions(req, ticket) # create_and_triage not in actions self.assertEqual([(1, 'create'), (0, 'create_and_assign')], actions) def test_transition_to_star_with_leave_operation(self): """Action is rendered by CTW for transition to * with leave_status """ config = self.env.config config.set('ticket-workflow', 'change_owner', 'assigned,closed -> *') config.set('ticket-workflow', 'change_owner.operations', 'leave_status,set_owner') self._reload_workflow() status = ['assigned', 'closed'] for s in status: ticket = Ticket(self.env) ticket['status'] = s ticket['owner'] = 'user2' ticket.insert() req = MockRequest(self.env, path_info='/ticket', method='POST', authname='user1') label, control, hints = \ self.ctlr.render_ticket_action_control(req, ticket, 'change_owner') self.assertEqual('change owner', label) self.assertEqual( 'to <input id="action_change_owner_reassign_owner" ' 'name="action_change_owner_reassign_owner" type="text" ' 'value="user1" />', unicode(control)) self.assertEqual( 'The owner will be changed from <span class="trac-author">' 'user2</span> to the specified user.', unicode(hints)) def test_leave_operation(self): ticket = Ticket(self.env) ticket['status'] = 'assigned' ticket['owner'] = 'user2' ticket.insert() req = MockRequest(self.env, path_info='/ticket', method='POST', authname='user1') label, control, hints = \ self.ctlr.render_ticket_action_control(req, ticket, 'leave') self.assertEqual('leave', label) self.assertEqual('as assigned', unicode(control)) self.assertEqual( 'The owner will remain <span class="trac-author">' 'user2</span>.', unicode(hints)) def test_get_actions_by_operation_for_req(self): """Request with no permission checking.""" req = MockRequest(self.env, path_info='/ticket/1') ticket = insert_ticket(self.env, status='new') actions = self.ctlr.get_actions_by_operation_for_req( req, ticket, 'set_owner') self.assertEqual([(0, u'change_owner'), (0, u'reassign')], actions) def test_get_actions_by_operation_for_req_with_ticket_modify(self): """User without TICKET_MODIFY won't have reassign action.""" req = MockRequest(self.env, authname='user1', path_info='/ticket/1') ticket = insert_ticket(self.env, status='new') actions = self.ctlr.get_actions_by_operation_for_req( req, ticket, 'set_owner') self.assertEqual([(0, u'change_owner')], actions) def test_get_actions_by_operation_for_req_without_ticket_modify(self): """User with TICKET_MODIFY will have reassign action.""" PermissionSystem(self.env).grant_permission('user1', 'TICKET_MODIFY') req = MockRequest(self.env, authname='user1', path_info='/ticket/1') ticket = insert_ticket(self.env, status='new') actions = self.ctlr.get_actions_by_operation_for_req( req, ticket, 'set_owner') self.assertEqual([(0, u'change_owner'), (0, u'reassign')], actions)
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 TicketSystemTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True) self.perm = PermissionSystem(self.env) self.ticket_system = TicketSystem(self.env) self.req = MockRequest(self.env) def tearDown(self): self.env.reset_db() def _get_actions(self, ticket_dict): ts = TicketSystem(self.env) ticket = insert_ticket(self.env, **ticket_dict) return ts.get_available_actions(self.req, Ticket(self.env, ticket.id)) def _get_ticket_field(self, field_name): fields = TicketSystem(self.env).get_ticket_fields() return next((i for i in fields if i['name'] == field_name)) def test_custom_field_text(self): self.env.config.set('ticket-custom', 'test', 'text') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'text', 'label': 'Test', 'value': 'Foo bar', 'max_size': 0, 'order': 0, 'format': 'wiki', 'custom': True}, fields[0]) def test_custom_field_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', 'option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0, 'custom': True}, fields[0]) def test_custom_field_optional_select(self): self.env.config.set('ticket-custom', 'test', 'select') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '1') self.env.config.set('ticket-custom', 'test.options', '|option1|option2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'select', 'label': 'Test', 'value': '1', 'options': ['option1', 'option2'], 'order': 0, 'optional': True, 'custom': True}, fields[0]) def test_custom_field_textarea(self): self.env.config.set('ticket-custom', 'test', 'textarea') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', 'Foo bar') self.env.config.set('ticket-custom', 'test.rows', '4') self.env.config.set('ticket-custom', 'test.format', 'wiki') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'textarea', 'label': 'Test', 'value': 'Foo bar', 'height': 4, 'order': 0, 'max_size': 0, 'format': 'wiki', 'custom': True}, fields[0]) def test_description_field(self): field = self._get_ticket_field('description') self.assertEqual({'name': 'description', 'label': 'Description', 'type': 'textarea', 'format': 'wiki'}, field) def test_custom_field_checkbox(self): def add_checkbox(name, value): self.env.config.set('ticket-custom', name, 'checkbox') self.env.config.set('ticket-custom', '%s.value' % name, value) add_checkbox('checkbox0', 'true') add_checkbox('checkbox1', 1) add_checkbox('checkbox2', 'enabled') add_checkbox('checkbox3', 0) add_checkbox('checkbox4', 'tru') add_checkbox('checkbox5', 'off') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'checkbox0', 'type': 'checkbox', 'label': 'Checkbox0', 'value': '1', 'order': 0, 'custom': True}, fields[0]) self.assertEqual('1', fields[1]['value']) self.assertEqual('1', fields[2]['value']) self.assertEqual('0', fields[3]['value']) self.assertEqual('0', fields[4]['value']) self.assertEqual('0', fields[5]['value']) def test_custom_field_time(self): self.env.config.set('ticket-custom', 'test', 'time') self.env.config.set('ticket-custom', 'test.label', 'Test') self.env.config.set('ticket-custom', 'test.value', '') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual({'name': 'test', 'type': 'time', 'label': 'Test', 'value': '', 'order': 0, 'format': 'datetime', 'custom': True}, fields[0]) def test_custom_field_with_invalid_name(self): ticket_custom = self.env.config['ticket-custom'] ticket_custom.set('_field1', 'text') ticket_custom.set('2field', 'text') ticket_custom.set('f3%^&*', 'text') ticket_custom.set('field4', 'text') ticket_custom.set('FiEld5', 'text') ts = TicketSystem(self.env) custom_fields = ts.custom_fields fields = ts.fields self.assertEqual(2, len(custom_fields)) self.assertIsNotNone(custom_fields.by_name('field4')) self.assertIsNotNone(custom_fields.by_name('field5')) self.assertIsNotNone(fields.by_name('field4')) self.assertIsNotNone(fields.by_name('field5')) self.assertIn( ('WARNING', u'Invalid name for custom field: "_field1" (ignoring)'), self.env.log_messages) self.assertIn( ('WARNING', u'Invalid name for custom field: "2field" (ignoring)'), self.env.log_messages) self.assertIn( ('WARNING', u'Invalid name for custom field: "f3%^&*" (ignoring)'), self.env.log_messages) def test_custom_field_with_reserved_name(self): ticket_custom = self.env.config['ticket-custom'] ticket_custom.set('owner', 'select') ticket_custom.set('description', 'text') ts = TicketSystem(self.env) custom_fields = ts.custom_fields self.assertIn( ('WARNING', u'Field name "owner" is a reserved name (ignoring)'), self.env.log_messages) self.assertIn( ('WARNING', u'Field name "description" is a reserved name (ignoring)'), self.env.log_messages) self.assertEqual({'name': 'owner', 'label': 'Owner', 'type': 'text'}, ts.fields.by_name('owner')) self.assertEqual({'name': 'description', 'label': 'Description', 'type': 'textarea', 'format': 'wiki'}, ts.fields.by_name('description')) self.assertIsNone(custom_fields.by_name('owner')) self.assertIsNone(custom_fields.by_name('description')) def test_custom_field_order(self): self.env.config.set('ticket-custom', 'test1', 'text') self.env.config.set('ticket-custom', 'test1.order', '2') self.env.config.set('ticket-custom', 'test2', 'text') self.env.config.set('ticket-custom', 'test2.order', '1') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual('test2', fields[0]['name']) self.assertEqual('test1', fields[1]['name']) def test_custom_field_label(self): self.env.config.set('ticket-custom', 'test_one', 'text') self.env.config.set('ticket-custom', 'test_two', 'text') self.env.config.set('ticket-custom', 'test_two.label', 'test_2') fields = TicketSystem(self.env).get_custom_fields() self.assertEqual('Test one', fields[0]['label']) self.assertEqual('test_2', fields[1]['label']) def _test_custom_field_with_enum(self, name, cls): tktsys = TicketSystem(self.env) instance = cls(self.env) instance.name = '%s 42' % name instance.insert() self.env.config.set('ticket-custom', name, 'text') field = self._get_ticket_field(name) self.assertFalse(field.get('custom')) with self.env.db_transaction: instances = list(cls.select(self.env)) if issubclass(cls, model.AbstractEnum): # delete from highest to lowest to avoid re-ordering enums instances.sort(reverse=True, key=lambda v: int(v.value)) for instance in instances: instance.delete() field = self._get_ticket_field(name) self.assertTrue(field.get('custom')) def test_custom_field_type(self): self._test_custom_field_with_enum('type', model.Type) def test_custom_field_priority(self): self._test_custom_field_with_enum('priority', model.Priority) def test_custom_field_milestone(self): self._test_custom_field_with_enum('milestone', Milestone) def test_custom_field_component(self): self._test_custom_field_with_enum('component', model.Component) def test_custom_field_version(self): self._test_custom_field_with_enum('version', Version) def test_custom_field_severity(self): self._test_custom_field_with_enum('severity', model.Severity) def test_custom_field_resolution(self): self._test_custom_field_with_enum('resolution', model.Resolution) def test_available_actions_full_perms(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.perm.grant_permission('anonymous', 'TICKET_MODIFY') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'new'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave', 'resolve', 'reassign', 'accept'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_no_perms(self): self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'})) def test_available_actions_create_only(self): self.perm.grant_permission('anonymous', 'TICKET_CREATE') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave', 'reopen'], self._get_actions({'status': 'closed'})) def test_available_actions_chgprop_only(self): # CHGPROP is not enough for changing a ticket's state (#3289) self.perm.grant_permission('anonymous', 'TICKET_CHGPROP') self.req.perm = PermissionCache(self.env) self.assertEqual(['leave'], self._get_actions({'status': 'new'})) self.assertEqual(['leave'], self._get_actions({'status': 'assigned'})) self.assertEqual(['leave'], self._get_actions({'status': 'accepted'})) self.assertEqual(['leave'], self._get_actions({'status': 'reopened'})) self.assertEqual(['leave'], self._get_actions({'status': 'closed'})) def test_get_allowed_owners_restrict_owner_false(self): self.env.config.set('ticket', 'restrict_owner', False) self.assertIsNone(self.ticket_system.get_allowed_owners()) def test_get_allowed_owners_restrict_owner_true(self): self.env.config.set('ticket', 'restrict_owner', True) self.env.insert_users([('user3', None, None), ('user1', None, None)]) self.perm.grant_permission('user4', 'TICKET_MODIFY') self.perm.grant_permission('user3', 'TICKET_MODIFY') self.perm.grant_permission('user2', 'TICKET_VIEW') self.perm.grant_permission('user1', 'TICKET_MODIFY') self.assertEqual(['user1', 'user3'], self.ticket_system.get_allowed_owners()) def test_get_ticket_fields_version_rename(self): """Cached ticket fields are updated when version is renamed.""" fields = self.ticket_system.get_ticket_fields() version_field = self._get_ticket_field('version') v2 = Version(self.env, '2.0') v2.name = '0.0' v2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_version_field = self._get_ticket_field('version') self.assertNotEqual(fields, updated_fields) self.assertEqual(['2.0', '1.0'], version_field['options']) self.assertEqual(['1.0', '0.0'], updated_version_field['options']) def test_get_ticket_fields_version_update_time(self): """Cached ticket fields are updated when version release time is changed. """ fields = self.ticket_system.get_ticket_fields() version_field = self._get_ticket_field('version') v1 = Version(self.env, '1.0') v1.time = datetime_now(utc) v2 = Version(self.env, '2.0') v2.time = v1.time - timedelta(seconds=1) v1.update() v2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_version_field = self._get_ticket_field('version') self.assertNotEqual(fields, updated_fields) self.assertEqual(['2.0', '1.0'], version_field['options']) self.assertEqual(['1.0', '2.0'], updated_version_field['options']) def test_get_ticket_fields_milestone_rename(self): """Cached ticket fields are updated when milestone is renamed.""" fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.name = 'milestone5' m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual(['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual(['milestone1', 'milestone3', 'milestone4', 'milestone5'], updated_milestone_field['options']) def test_get_ticket_fields_milestone_update_completed(self): """Cached ticket fields are updated when milestone is completed date is changed. """ fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.completed = datetime_now(utc) m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual(['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual(['milestone2', 'milestone1', 'milestone3', 'milestone4'], updated_milestone_field['options']) def test_get_ticket_fields_milestone_update_due(self): """Cached ticket fields are updated when milestone due date is changed. """ fields = self.ticket_system.get_ticket_fields() milestone_field = self._get_ticket_field('milestone') m2 = Milestone(self.env, 'milestone2') m2.due = datetime_now(utc) m2.update() updated_fields = self.ticket_system.get_ticket_fields() updated_milestone_field = self._get_ticket_field('milestone') self.assertNotEqual(fields, updated_fields) self.assertEqual(['milestone1', 'milestone2', 'milestone3', 'milestone4'], milestone_field['options']) self.assertEqual(['milestone2', 'milestone1', 'milestone3', 'milestone4'], updated_milestone_field['options']) def test_resource_exists_valid_resource_id(self): insert_ticket(self.env) r1 = Resource('ticket', 1) r2 = Resource('ticket', 2) self.assertTrue(self.ticket_system.resource_exists(r1)) self.assertFalse(self.ticket_system.resource_exists(r2)) def test_resource_exists_invalid_resource_id(self): """Exception is trapped from resource with invalid id.""" r1 = Resource('ticket', None) r2 = Resource('ticket', 'abc') r3 = Resource('ticket', '2.') r4 = Resource('ticket', r2) self.assertFalse(self.ticket_system.resource_exists(r1)) self.assertFalse(self.ticket_system.resource_exists(r2)) self.assertFalse(self.ticket_system.resource_exists(r3)) self.assertFalse(self.ticket_system.resource_exists(r4))
class SetOwnerToSelfAttributeTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(default_data=True) self.ctlr = TicketSystem(self.env).action_controllers[0] self.req = MockRequest(self.env, authname='user1') ps = PermissionSystem(self.env) for user in ('user1', 'user2'): ps.grant_permission(user, 'TICKET_MODIFY') self.env.insert_users([('user1', 'User 1', None), ('user2', 'User 2', None)]) def _get_ticket_actions(self, req, ticket): return [action[1] for action in self.ctlr.get_ticket_actions(req, ticket)] def _reload_workflow(self): self.ctlr.actions = self.ctlr.get_all_actions() def _insert_ticket(self, status, owner, resolution=None): ticket = Ticket(self.env) ticket['status'] = status ticket['owner'] = owner if resolution: ticket['resolution'] = resolution ticket.insert() return ticket def test_owner_is_other(self): """Ticket owner is not auth'ed user. The workflow action is shown when the state will be changed by the action. """ ticket = self._insert_ticket('accepted', 'user2') args = self.req, ticket, 'accept' label, control, hints = self.ctlr.render_ticket_action_control(*args) ticket_actions = self._get_ticket_actions(*args[0:2]) self.assertIn('accept', ticket_actions) self.assertEqual(label, 'accept') self.assertEqual('', unicode(control)) self.assertEqual('The owner will be changed from ' '<span class="trac-author">User 2</span> to ' '<span class="trac-author-user">User 1</span>.', unicode(hints)) def test_owner_is_self_and_state_change(self): """Ticket owner is auth'ed user with state change. The workflow action is shown when the state will be changed by the action, even when the ticket owner is the authenticated user. """ ticket = self._insert_ticket('new', 'user1') args = self.req, ticket, 'accept' label, control, hints = self.ctlr.render_ticket_action_control(*args) ticket_actions = self._get_ticket_actions(*args[0:2]) self.assertIn('accept', ticket_actions) self.assertEqual(label, 'accept') self.assertEqual('', unicode(control)) self.assertEqual('The owner will remain <span class="trac-author-user">' 'User 1</span>.', unicode(hints)) def test_owner_is_self_and_no_state_change(self): """Ticket owner is the auth'ed user and no state change. The ticket action is not in the list of available actions when the state will not be changed by the action and the ticket owner is the authenticated user. """ ticket = self._insert_ticket('accepted', 'user1') args = self.req, ticket, 'accept' ticket_actions = self._get_ticket_actions(*args[0:2]) self.assertNotIn('accept', ticket_actions) def test_owner_is_self_state_change_and_multiple_operations(self): """Ticket owner is auth'ed user, state change and multiple ops. The set_owner_to_self workflow hint is shown when the ticket status is changed by the action, even when the ticket owner is the authenticated user. """ ticket = self._insert_ticket('new', 'user1') workflow = self.env.config['ticket-workflow'] workflow.set('resolve_as_owner', '* -> closed') workflow.set('resolve_as_owner.operations', 'set_owner_to_self, set_resolution') workflow.set('resolve_as_owner.set_resolution', 'fixed') self._reload_workflow() args = self.req, ticket, 'resolve_as_owner' label, control, hints = self.ctlr.render_ticket_action_control(*args) ticket_actions = self._get_ticket_actions(*args[0:2]) self.assertIn('resolve_as_owner', ticket_actions) self.assertEqual(label, 'resolve as owner') self.assertEqual( 'as fixed<input id="action_resolve_as_owner_resolve_resolution" ' 'name="action_resolve_as_owner_resolve_resolution" type="hidden" ' 'value="fixed" />', unicode(control)) self.assertEqual( 'The owner will remain <span class="trac-author-user">User 1' '</span>. The resolution will be set to fixed.', unicode(hints)) def test_owner_is_self_no_state_change_and_multiple_operations(self): """Ticket owner is auth'ed user, no state change and multiple ops. The set_owner_to_self workflow hint is not shown when the ticket state is not changed by the action and the ticket owner is the authenticated user. """ ticket = self._insert_ticket('closed', 'user1', 'fixed') workflow = self.env.config['ticket-workflow'] workflow.set('fix_resolution', 'closed -> closed') workflow.set('fix_resolution.operations', 'set_owner_to_self, set_resolution') workflow.set('fix_resolution.set_resolution', 'invalid') self._reload_workflow() args = self.req, ticket, 'fix_resolution' label, control, hints = self.ctlr.render_ticket_action_control(*args) ticket_actions = self._get_ticket_actions(*args[0:2]) self.assertIn('fix_resolution', ticket_actions) self.assertEqual(label, 'fix resolution') self.assertEqual( 'as invalid<input id="action_fix_resolution_resolve_resolution" ' 'name="action_fix_resolution_resolve_resolution" type="hidden" ' 'value="invalid" />', unicode(control)) self.assertEqual('The resolution will be set to invalid.', unicode(hints))
class FormatAuthorTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=['trac.web.chrome.*', 'trac.perm.*', 'tracopt.perm.authz_policy']) self.env.config.set('trac', 'permission_policies', 'AuthzPolicy, DefaultPermissionPolicy') fd, self.authz_file = tempfile.mkstemp() with os.fdopen(fd, 'w') as f: f.write("""\ [wiki:WikiStart] user2 = EMAIL_VIEW [wiki:TracGuide] user2 = """) PermissionSystem(self.env).grant_permission('user1', 'EMAIL_VIEW') self.env.config.set('authz_policy', 'authz_file', self.authz_file) def tearDown(self): os.remove(self.authz_file) def test_subject_is_anonymous(self): format_author = Chrome(self.env).format_author self.assertEqual('anonymous', format_author(None, 'anonymous')) def test_subject_is_none(self): format_author = Chrome(self.env).format_author self.assertEqual('(none)', format_author(None, None)) def test_actor_has_email_view(self): req = MockRequest(self.env, authname='user1') author = Chrome(self.env).format_author(req, '*****@*****.**') self.assertEqual('*****@*****.**', author) def test_actor_no_email_view(self): req = MockRequest(self.env, authname='user2') author = Chrome(self.env).format_author(req, '*****@*****.**') self.assertEqual(u'user@\u2026', author) def test_actor_no_email_view_show_email_addresses(self): self.env.config.set('trac', 'show_email_addresses', True) req = MockRequest(self.env, authname='user2') author = Chrome(self.env).format_author(req, '*****@*****.**') self.assertEqual('*****@*****.**', author) def test_actor_no_email_view_no_req(self): author = Chrome(self.env).format_author(None, '*****@*****.**') self.assertEqual(u'user@\u2026', author) def test_actor_has_email_view_for_resource(self): format_author = Chrome(self.env).format_author req = MockRequest(self.env, authname='user2') resource = Resource('wiki', 'WikiStart') author = format_author(req, '*****@*****.**', resource) self.assertEqual('*****@*****.**', author) def test_actor_has_email_view_for_resource_negative(self): format_author = Chrome(self.env).format_author req = MockRequest(self.env, authname='user2') resource = Resource('wiki', 'TracGuide') author = format_author(req, '*****@*****.**', resource) self.assertEqual(u'user@\u2026', author) def test_show_full_names_true(self): format_author = Chrome(self.env).format_author self.env.config.set('trac', 'show_full_names', True) self.env.insert_users([ ('user1', 'User One', '*****@*****.**'), ('user2', None, None) ]) self.assertEqual('User One', format_author(None, 'user1')) self.assertEqual('user2', format_author(None, 'user2')) def test_show_full_names_false(self): format_author = Chrome(self.env).format_author self.env.config.set('trac', 'show_full_names', False) self.assertEqual('user1', format_author(None, 'user1')) self.assertEqual('user2', format_author(None, 'user2')) def test_show_email_true(self): format_author = Chrome(self.env).format_author req = MockRequest(self.env, authname='user2') author = format_author(None, '*****@*****.**', show_email=True) self.assertEqual('*****@*****.**', author) author = format_author(req, '*****@*****.**', show_email=True) self.assertEqual('*****@*****.**', author) def test_show_email_false(self): format_author = Chrome(self.env).format_author req = MockRequest(self.env, authname='user1') author = format_author(None, '*****@*****.**', show_email=False) self.assertEqual(u'user@\u2026', author) author = format_author(req, '*****@*****.**', show_email=False) self.assertEqual(u'user@\u2026', author) def test_show_full_names_true_actor_has_email_view(self): format_author = Chrome(self.env).format_author self.env.config.set('trac', 'show_full_names', True) self.env.insert_users([ ('user1', 'User One', '*****@*****.**'), ('user2', None, None) ]) self.assertEqual('User One', format_author(None, 'user1')) self.assertEqual('user2', format_author(None, 'user2')) def test_show_full_names_false_actor_has_email_view(self): req = MockRequest(self.env, authname='user1') format_author = Chrome(self.env).format_author self.env.config.set('trac', 'show_full_names', False) self.assertEqual('user1', format_author(req, 'user1')) self.assertEqual('user2', format_author(req, 'user2')) def test_show_email_addresses_true(self): req = MockRequest(self.env) format_author = Chrome(self.env).format_author self.env.config.set('trac', 'show_email_addresses', True) self.assertEqual('*****@*****.**', format_author(None, '*****@*****.**')) self.assertEqual('*****@*****.**', format_author(req, '*****@*****.**')) def test_show_email_addresses_false(self): req = MockRequest(self.env) format_author = Chrome(self.env).format_author self.env.config.set('trac', 'show_email_addresses', False) self.assertEqual(u'user3@\u2026', format_author(None, '*****@*****.**')) self.assertEqual('*****@*****.**', format_author(req, '*****@*****.**')) def test_format_emails(self): format_emails = Chrome(self.env).format_emails to_format = '[email protected], user2; [email protected]' self.assertEqual(u'user1@\u2026, user2, user3@\u2026', format_emails(None, to_format)) def test_format_emails_actor_has_email_view(self): req = MockRequest(self.env, authname='user1') context = web_context(req) format_emails = Chrome(self.env).format_emails to_format = '[email protected], user2; [email protected]' self.assertEqual('[email protected], user2, [email protected]', format_emails(context, to_format)) def test_format_emails_actor_no_email_view(self): req = MockRequest(self.env, authname='user2') context = web_context(req) format_emails = Chrome(self.env).format_emails to_format = '[email protected], user2; [email protected]' self.assertEqual(u'user1@\u2026, user2, user3@\u2026', format_emails(context, to_format))
class test_commitupdater(unittest.TestCase): def _test_authenticated_session(self, username, fullname, email): """ Verifies that a session cookie does not get used if the user is logged in, and that Trac expires the cookie. """ req = MockRequest(self.env, authname=username) req.incookie['trac_session'] = '123456' session = Session(self.env, req) self.assertEqual(username, session.sid) session['email'] = email session['name'] = fullname session.save() def setUp(self): self.env = \ EnvironmentStub(enable=['trac.attachment.LegacyAttachmentPolicy', 'trac.perm.*', 'trac.wiki.web_ui.ReadonlyWikiPolicy', 'trac.ticket.*']) self.policy = ReadonlyWikiPolicy(self.env) store = perm.DefaultPermissionStore(self.env) self.perm_sys = perm.PermissionSystem(self.env) users = [('user1', 'User C', '*****@*****.**'), ('user2', 'User A', '*****@*****.**'), ('user3', 'User D', '*****@*****.**'), ('user4', 'User B', '*****@*****.**')] self.env.insert_users(users) store.grant_permission('user1', 'TICKET_MODIFY') store.grant_permission('user2', 'TICKET_VIEW') store.grant_permission('user3', 'TICKET_MODIFY') store.grant_permission('user4', 'TICKET_MODIFY') for (username, fullname, email) in users: self._test_authenticated_session(username, fullname, email) self.repo = Mock(MockRepository, 'testrepo', {'name': 'testrepo', 'id': 4321}, None) # Set all component objects to defaults config = self.env.config config.set("ticket","commit_ticket_update_commands.close","close closed closes fix fixed fixes") config.set("ticket","commit_ticket_update_commands.implements","implement implements implemented impl") config.set("ticket","commit_ticket_update_commands.invalidate","invalid invalidate invalidated invalidates") config.set("ticket","commit_ticket_update_commands.refs","addresses re references refs see") config.set("ticket","commit_ticket_update_commands.rejects","reject rejects rejected") config.set("ticket","commit_ticket_update_commands.worksforme","worksforme") config.set("ticket","commit_ticket_update_commands.alreadyimplemented","alreadyimplemented already_implemented") config.set("ticket","commit_ticket_update_commands.reopen","reopen reopens reopened") config.set("ticket","commit_ticket_update_commands.testready","testready test_ready ready_for_test rft") config.set("ticket","commit_ticket_update_allowed_domains","example.org mydomain.net") #config.set("ticket","commit_ticket_update_check_perms",False) self._add_component('component3', 'user3') self.ticket = Ticket(self.env) self.ticket.populate({ 'reporter': 'user1', 'summary': 'the summary', 'component': 'component3', 'owner': 'user3', 'status': 'new', }) self.tkt_id = self.ticket.insert() self.ticket2 = Ticket(self.env) self.ticket2.populate({ 'reporter': 'user2', 'summary': 'the summary', 'component': 'component3', 'owner': 'user2', 'status': 'new', }) self.tkt2_id = self.ticket2.insert() #for username, name, email in self.env.get_known_users(): # sys.stderr.write('known user %s, %s, %s\n' % (username, name, email)) with self.env.db_transaction as db: db("INSERT INTO enum VALUES ('resolution', 'already_implemented', 6)") #db("INSERT INTO enum VALUES ('resolution', 'worksforme', 5)") self._committicketupdater = CommitTicketUpdater(self.env) def noop(self): pass def tearDown(self): self.env.reset_db() def _add_component(self, name='test', owner='owner1'): component = Component(self.env) component.name = name component.owner = owner component.insert() def build_comment(self,changeset): revstring = str(changeset.rev) drev = str(self.repo.display_rev(changeset.rev)) if self.repo.name: revstring += '/' + self.repo.name drev += '/' + self.repo.name return """In [changeset:"%s" %s]: {{{ #!CommitTicketReference repository="%s" revision="%s" %s }}}""" % (revstring, drev, self.repo.name, changeset.rev, changeset.message) def check_ticket_comment(self,changeset): for obj in [self.env]: #print('comment=%s' % self.build_comment(changeset), file=sys.stderr) self.assertEqual(self._committicketupdater.make_ticket_comment(self.repo,changeset), self.build_comment(changeset)) def test_check_closes(self): message = "Fixed some stuff. closes #%i" % self.tkt_id test_changeset = Mock(Changeset, self.repo, 42, message, '*****@*****.**', None) self.check_ticket_comment(test_changeset) # For each object in turn: # Get tickets and commands tickets = self._committicketupdater._parse_message(message) # First, check we've got the tickets we were expecting self.assertEqual(tickets.keys(),[self.tkt_id]) # Now check the actions are right self.assertEqual(tickets.get(self.tkt_id),[self._committicketupdater.cmd_close]) ret = ret = self._committicketupdater.changeset_added_impl(self.repo, test_changeset) (cmds, ticket) = ret[self.tkt_id] self.assertEqual(ticket['status'], 'closed') self.assertEqual(ticket['owner'], 'user1') self.assertEqual(ticket['resolution'], 'fixed') def test_check_implements(self): message = "Fixed some stuff. implements #%i" % self.tkt_id test_changeset = Mock(Changeset, self.repo, 42, message, '*****@*****.**', None) self.check_ticket_comment(test_changeset) # For each object in turn: # Get tickets and commands tickets = self._committicketupdater._parse_message(message) # First, check we've got the tickets we were expecting self.assertEqual(tickets.keys(),[self.tkt_id]) # Now check the actions are right self.assertEqual(tickets.get(self.tkt_id),[self._committicketupdater.cmd_implements]) ret = self._committicketupdater.changeset_added_impl(self.repo, test_changeset) (cmds, ticket) = ret[self.tkt_id] self.assertEqual(ticket['status'], 'implemented') self.assertEqual(ticket['owner'], 'user1') def test_check_invalidate(self): message = "Fixed some stuff. invalid #%i" % self.tkt_id test_changeset = Mock(Changeset, self.repo, 42, message, '*****@*****.**', None) self.check_ticket_comment(test_changeset) # For each object in turn: # Get tickets and commands tickets = self._committicketupdater._parse_message(message) # First, check we've got the tickets we were expecting self.assertEqual(tickets.keys(),[self.tkt_id]) # Now check the actions are right self.assertEqual(tickets.get(self.tkt_id),[self._committicketupdater.cmd_invalidate]) ret = self._committicketupdater.changeset_added_impl(self.repo, test_changeset) (cmds, ticket) = ret[self.tkt_id] self.assertEqual(ticket['status'], 'closed') self.assertEqual(ticket['resolution'], 'invalid') def test_check_rejects(self): message = "Fixed some stuff. reject #%i" % self.tkt_id test_changeset = Mock(Changeset, self.repo, 42, message, '*****@*****.**', None) self.check_ticket_comment(test_changeset) # For each object in turn: # Get tickets and commands tickets = self._committicketupdater._parse_message(message) # First, check we've got the tickets we were expecting self.assertEqual(tickets.keys(),[self.tkt_id]) # Now check the actions are right self.assertEqual(tickets.get(self.tkt_id),[self._committicketupdater.cmd_rejects]) ret = self._committicketupdater.changeset_added_impl(self.repo, test_changeset) (cmds, ticket) = ret[self.tkt_id] self.assertEqual(ticket['status'], 'rejected') def test_check_worksforme(self): message = "Fixed some stuff. worksforme #%i" % self.tkt_id test_changeset = Mock(Changeset, self.repo, 42, message, '*****@*****.**', None) self.check_ticket_comment(test_changeset) # For each object in turn: # Get tickets and commands tickets = self._committicketupdater._parse_message(message) # First, check we've got the tickets we were expecting self.assertEqual(tickets.keys(),[self.tkt_id]) # Now check the actions are right self.assertEqual(tickets.get(self.tkt_id),[self._committicketupdater.cmd_worksforme]) ret = self._committicketupdater.changeset_added_impl(self.repo, test_changeset) (cmds, ticket) = ret[self.tkt_id] self.assertEqual(ticket['status'], 'closed') self.assertEqual(ticket['resolution'], 'worksforme') def test_check_alreadyimplemented(self): message = "Fixed some stuff. alreadyimplemented #%i" % self.tkt_id test_changeset = Mock(Changeset, self.repo, 42, message, '*****@*****.**', None) self.check_ticket_comment(test_changeset) # For each object in turn: # Get tickets and commands tickets = self._committicketupdater._parse_message(message) # First, check we've got the tickets we were expecting self.assertEqual(tickets.keys(),[self.tkt_id]) # Now check the actions are right self.assertEqual(tickets.get(self.tkt_id),[self._committicketupdater.cmd_alreadyimplemented]) ret = self._committicketupdater.changeset_added_impl(self.repo, test_changeset) (cmds, ticket) = ret[self.tkt_id] self.assertEqual(ticket['status'], 'closed') self.assertEqual(ticket['resolution'], 'already_implemented') def test_check_already_implemented(self): message = "Fixed some stuff. already_implemented #%i" % self.tkt_id test_changeset = Mock(Changeset, self.repo, 42, message, '*****@*****.**', None) self.check_ticket_comment(test_changeset) # For each object in turn: # Get tickets and commands tickets = self._committicketupdater._parse_message(message) # First, check we've got the tickets we were expecting self.assertEqual(tickets.keys(),[self.tkt_id]) # Now check the actions are right self.assertEqual(tickets.get(self.tkt_id),[self._committicketupdater.cmd_alreadyimplemented]) ret = self._committicketupdater.changeset_added_impl(self.repo, test_changeset) (cmds, ticket) = ret[self.tkt_id] self.assertEqual(ticket['status'], 'closed') self.assertEqual(ticket['resolution'], 'already_implemented') def test_check_reopens(self): message = "Fixed some stuff. worksforme #%i" % self.tkt_id test_changeset = Mock(Changeset, self.repo, 42, message, '*****@*****.**', None) ret = self._committicketupdater.changeset_added_impl(self.repo, test_changeset) message = "Fixed some stuff. reopen #%i" % self.tkt_id test_changeset = Mock(Changeset, self.repo, 42, message, '*****@*****.**', None) self.check_ticket_comment(test_changeset) # For each object in turn: # Get tickets and commands tickets = self._committicketupdater._parse_message(message) # First, check we've got the tickets we were expecting self.assertEqual(tickets.keys(),[self.tkt_id]) # Now check the actions are right self.assertEqual(tickets.get(self.tkt_id),[self._committicketupdater.cmd_reopens]) ret = self._committicketupdater.changeset_added_impl(self.repo, test_changeset) (cmds, ticket) = ret[self.tkt_id] self.assertEqual(ticket['status'], 'reopened') def test_check_testready(self): message = "Fixed some stuff. ready_for_test #%i" % self.tkt_id test_changeset = Mock(Changeset, self.repo, 42, message, '*****@*****.**', None) self.check_ticket_comment(test_changeset) # For each object in turn: # Get tickets and commands tickets = self._committicketupdater._parse_message(message) # First, check we've got the tickets we were expecting self.assertEqual(tickets.keys(),[self.tkt_id]) # Now check the actions are right self.assertEqual(tickets.get(self.tkt_id),[self._committicketupdater.cmd_testready]) ret = self._committicketupdater.changeset_added_impl(self.repo, test_changeset) (cmds, ticket) = ret[self.tkt_id] self.assertEqual(ticket['status'], 'test_ready') def test_allowed_domains(self): message = "Fixed some stuff. reopen #%i" % self.tkt_id test_changeset_declined = Mock(Changeset, self.repo, 42, message, "test_person <*****@*****.**>", None) self.assertEqual(self._committicketupdater._is_author_allowed(test_changeset_declined.author),False) test_changeset_allowed = Mock(Changeset, self.repo, 42, message, "test_person <*****@*****.**>", None) self.assertEqual(self._committicketupdater._is_author_allowed(test_changeset_allowed.author),True) test_changeset_no_domain = Mock(Changeset, self.repo, 42, message, "test_person", None) self.assertEqual(self._committicketupdater._is_author_allowed(test_changeset_no_domain.author),False) message = "Fixed some stuff. fixed #%i" % self.tkt_id test_changeset = Mock(Changeset, self.repo, 42, message, 'test_person <*****@*****.**>', None) self.check_ticket_comment(test_changeset) # For each object in turn: # Get tickets and commands tickets = self._committicketupdater._parse_message(message) # First, check we've got the tickets we were expecting self.assertEqual(tickets.keys(),[self.tkt_id]) ret = self._committicketupdater.changeset_added_impl(self.repo, test_changeset) (cmds, ticket) = ret[self.tkt_id] self.assertEqual(ticket['status'], 'new') def test_check_closes_multiple(self): message = "Fixed some stuff. closes #%i, #%i" % (self.tkt_id, self.tkt2_id) test_changeset = Mock(Changeset, self.repo, 42, message, '*****@*****.**', None) self.check_ticket_comment(test_changeset) # For each object in turn: # Get tickets and commands tickets = self._committicketupdater._parse_message(message) # First, check we've got the tickets we were expecting self.assertEqual(tickets.keys(),[self.tkt_id, self.tkt2_id]) # Now check the actions are right self.assertEqual(tickets.get(self.tkt_id),[self._committicketupdater.cmd_close]) self.assertEqual(tickets.get(self.tkt2_id),[self._committicketupdater.cmd_close]) ret = ret = self._committicketupdater.changeset_added_impl(self.repo, test_changeset) (cmds, ticket) = ret[self.tkt_id] self.assertEqual(ticket['status'], 'closed') self.assertEqual(ticket['owner'], 'user1') self.assertEqual(ticket['resolution'], 'fixed') (cmds, ticket2) = ret[self.tkt2_id] self.assertEqual(ticket2['status'], 'closed') self.assertEqual(ticket2['owner'], 'user1') self.assertEqual(ticket2['resolution'], 'fixed') def test_check_closes_non_existing_ticket(self): non_existing_ticket_id = 12345 message = "Fixed some stuff. closes #%i" % non_existing_ticket_id test_changeset = Mock(Changeset, self.repo, 42, message, '*****@*****.**', None) self.check_ticket_comment(test_changeset) # For each object in turn: # Get tickets and commands tickets = self._committicketupdater._parse_message(message) # First, check we've got the tickets we were expecting self.assertEqual(tickets.keys(),[non_existing_ticket_id]) # Now check the actions are right self.assertEqual(tickets.get(non_existing_ticket_id),[self._committicketupdater.cmd_close]) ret = ret = self._committicketupdater.changeset_added_impl(self.repo, test_changeset) (cmds, ticket) = ret[non_existing_ticket_id] self.assertEqual(ticket, None) def test_check_closes_with_full_email_addr(self): message = "Fixed some stuff. closes #%i" % (self.tkt_id) test_changeset = Mock(Changeset, self.repo, 42, message, 'User One <*****@*****.**>', None) self.check_ticket_comment(test_changeset) # For each object in turn: # Get tickets and commands tickets = self._committicketupdater._parse_message(message) # First, check we've got the tickets we were expecting self.assertEqual(tickets.keys(),[self.tkt_id]) # Now check the actions are right self.assertEqual(tickets.get(self.tkt_id),[self._committicketupdater.cmd_close]) ret = ret = self._committicketupdater.changeset_added_impl(self.repo, test_changeset) (cmds, ticket) = ret[self.tkt_id] self.assertEqual(ticket['status'], 'closed') self.assertEqual(ticket['owner'], 'user1') self.assertEqual(ticket['resolution'], 'fixed')