def test_props_format_wrap_leftside_unicode(self): self.env.config.set("notification", "mime_encoding", "none") ticket = Ticket(self.env) ticket["summary"] = u"This is a summary" ticket["reporter"] = u"anonymous" ticket["status"] = u"new" ticket["owner"] = u"somebody" ticket["type"] = u"defect" ticket["priority"] = u"major" ticket["milestone"] = u"milestone1" ticket["component"] = ( u"Trac は BSD ライセンスのもとで配" u"布されています。[1:]このライセ" u"ンスの全文は、配布ファイルに" u"含まれている [3:COPYING] ファイル" u"と同じものが[2:オンライン]で参" u"照できます。" ) ticket["version"] = u"2.0" ticket["resolution"] = u"fixed" ticket["keywords"] = u"" ticket.insert() formatted = """\ Reporter: anonymous | Owner: somebody Type: defect | Status: new Priority: major | Milestone: milestone1 Component: Trac は BSD ライセンスのもとで配布 | Version: 2.0 されています。[1:]このライセンスの全文は、配 | Keywords: 布ファイルに含まれている [3:COPYING] ファイル | と同じものが[2:オンライン]で参照できます。 | Resolution: fixed |""" self._validate_props_format(formatted, ticket)
def _change_ticket(self, id, history): ticket = Ticket(self.env, id) keys = history.keys() keys.sort() for key in keys: ticket['hours_remaining'] = history[key] ticket.save_changes("me", "testing", time.mktime(key.timetuple()))
def wake_up(self, *args): db = self.env.get_db_cnx() cursor = db.cursor() # find still opened more recent milestone # select ticket whom milestone are due in less than specified delay cursor.execute(""" SELECT m.name FROM milestone m WHERE m.completed is NULL or m.completed = 0 AND m.due not NULL and m.due > 0 ORDER BY m.due ASC LIMIT 1 """ ) next_milestone = None for name, in cursor: next_milestone = name # select ticket whom milestone are due in less than specified delay cursor.execute(""" SELECT t.id , t.milestone FROM ticket t, milestone m WHERE t.status != 'closed' AND t.milestone = m.name AND m.completed not NULL and m.completed > 0 """ ) if next_milestone: for id, milestone in cursor: mess = "ticket %s is opened in closed milestone %s. Should postpone this ticket to %s" % (id, milestone, next_milestone) self.env.log.debug(mess) ticket = Ticket(self.env, id) ticket.populate({'milestone':next_milestone}) ticket.save_changes(self.getId(),mess) else: self.env.log.debug("No opened milestone found. Cannot postpone tickets")
def test_get_tickets(self): for pdata in ( {'prefix': 'p2', 'name':'product, too', 'description': ''}, {'prefix': 'p3', 'name':'strike three', 'description': ''}, ): num_tickets = 5 product = Product(self.global_env) product._data.update(pdata) product.insert() self.env = ProductEnvironment(self.global_env, product) for i in range(num_tickets): ticket = Ticket(self.env) ticket['summary'] = 'hello ticket #%s-%d' % (product.prefix, i) ticket['reporter'] = 'admin' tid = ticket.insert() # retrieve tickets using both global and product scope tickets_from_global = [(t['product'], t['id']) for t in Product.get_tickets(self.global_env, product.prefix)] self.assertEqual(len(tickets_from_global), num_tickets) tickets_from_product = [(t['product'], t['id']) for t in Product.get_tickets(self.env)] self.assertEqual(len(tickets_from_product), num_tickets) # both lists should contain same elements intersection = set(tickets_from_global) & set(tickets_from_product) self.assertEqual(len(intersection), num_tickets)
def set_resource_tags(self, req, ticket_or_resource, tags, comment=u'', when=None): try: resource = ticket_or_resource.resource except AttributeError: resource = ticket_or_resource assert resource.realm == self.realm if not self._check_permission(req, resource, 'modify'): raise PermissionError(resource=resource, env=self.env) tag_set = set(tags) # Processing a call from TracTags, try to alter the ticket. tkt = Ticket(self.env, resource.id) all = self._ticket_tags(tkt) # Avoid unnecessary ticket changes, considering comments below. if tag_set != all: # Will only alter tags in 'keywords' ticket field. keywords = split_into_tags(tkt['keywords']) # Assume, that duplication is depreciated and consolitation # wanted to primarily affect 'keywords' ticket field. # Consequently updating ticket tags and reducing (tag) # 'ticket_fields' afterwards may result in undesired tag loss. tag_set.difference_update(all.difference(keywords)) tkt['keywords'] = u' '.join(sorted(map(to_unicode, tag_set))) tkt.save_changes(get_reporter_id(req), comment) else: # Processing a change listener event. tags = self._ticket_tags(ticket_or_resource) super(TicketTagProvider, self).set_resource_tags(req, resource, tags)
def ticket_created(self, ticket): # Check for loops if self._check_ticket(ticket): return linkmap = {} ticket_copy = Ticket(self.env, ticket.id) ticket_copy['tracforge_linkmap'] = serialize_map({self.env.path: ticket.id}) ticket_copy.tracforge_seen = ticket.tracforge_seen subscribers = SubscriptionManager(self.env).get_subscribers('ticket') for subscriber in subscribers: env = open_env(subscriber) self.log.debug('Pushing ticket number %s to %s'%(ticket_copy.id,env.path)) id = TicketSubscribable(env)._ticket_created(ticket_copy) linkmap[env.path] = id db = self.env.get_db_cnx() cursor = db.cursor() self.log.debug('value = %s' % serialize_map(linkmap)) cursor.execute("UPDATE ticket_custom SET value=%s " "WHERE ticket = %s AND name = %s" , (serialize_map(linkmap), ticket.id, 'tracforge_linkmap')) db.commit()
def test_owner_from_changed_component(self): """ Verify that the owner of a new ticket is updated when the component is changed. """ component1 = Component(self.env) component1.name = 'test1' component1.owner = 'joe' component1.insert() component2 = Component(self.env) component2.name = 'test2' component2.owner = 'kate' component2.insert() ticket = Ticket(self.env) ticket['reporter'] = 'santa' ticket['summary'] = 'Foo' ticket['component'] = 'test1' ticket['status'] = 'new' tktid = ticket.insert() ticket = Ticket(self.env, tktid) ticket['component'] = 'test2' ticket.save_changes('jane', 'Testing') self.assertEqual('kate', ticket['owner'])
def test_set_tags(self): tags = ['tag3'] ticket = Ticket(self.env, 1) ticket['keywords'] = tags[0] # Tags get updated by TicketChangeListener method. ticket.save_changes(self.req.authname) self.assertEquals(self.tag_sys.get_all_tags(self.req).keys(), tags)
def ticket_setup(tc): ticket = Ticket(tc.env) ticket['reporter'] = 'santa' ticket['summary'] = 'This is the summary' ticket.insert() ticket['status'] = 'new' ticket.save_changes('claus', 'set status', 0)
def get_changelog( self , ticketid): t = Ticket(self.macroenv.tracenv, ticketid) try: return( t.get_changelog() ) except: self.macroenv.tracenv.log.warning("get_changelog failed on ticket %s", ticketid) return [] # no changelogs
def _test_short_login(enabled): ticket = Ticket(self.env) ticket['reporter'] = 'joeuser' ticket['summary'] = 'This is a summary' ticket.insert() # Be sure that at least one email address is valid, so that we # send a notification even if other addresses are not valid self.env.config.set('notification', 'smtp_always_cc', '*****@*****.**') if enabled: self.env.config.set('notification', 'use_short_addr', 'true') tn = TicketNotifyEmail(self.env) tn.notify(ticket, newticket=True) message = notifysuite.smtpd.get_message() (headers, body) = parse_smtp_message(message) # Msg should not have a 'To' header if not enabled: self.failIf('To' in headers) else: tolist = [addr.strip() for addr in headers['To'].split(',')] # Msg should have a 'Cc' field self.failIf('Cc' not in headers) cclist = [addr.strip() for addr in headers['Cc'].split(',')] if enabled: # Msg should be delivered to the reporter self.failIf(ticket['reporter'] not in tolist) else: # Msg should not be delivered to joeuser self.failIf(ticket['reporter'] in cclist) # Msg should still be delivered to the always_cc list self.failIf(self.env.config.get('notification', 'smtp_always_cc') not in cclist)
def test_date(self): """Date format compliance (RFC822) we do not support 'military' format""" date_str = r"^((?P<day>\w{3}),\s*)*(?P<dm>\d{2})\s+" \ r"(?P<month>\w{3})\s+(?P<year>200\d)\s+" \ r"(?P<hour>\d{2}):(?P<min>[0-5][0-9])" \ r"(:(?P<sec>[0-5][0-9]))*\s" \ r"((?P<tz>\w{2,3})|(?P<offset>[+\-]\d{4}))$" date_re = re.compile(date_str) # python time module does not detect incorrect time values days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'] months = ['Jan','Feb','Mar','Apr','May','Jun', \ 'Jul','Aug','Sep','Oct','Nov','Dec'] tz = ['UT','GMT','EST','EDT','CST','CDT','MST','MDT''PST','PDT'] ticket = Ticket(self.env) ticket['reporter'] = '"Joe User" <*****@*****.**>' ticket['summary'] = 'This is a summary' ticket.insert() tn = TicketNotifyEmail(self.env) tn.notify(ticket, newticket=True) message = notifysuite.smtpd.get_message() (headers, body) = parse_smtp_message(message) self.failIf('Date' not in headers) mo = date_re.match(headers['Date']) self.failIf(not mo) if mo.group('day'): self.failIf(mo.group('day') not in days) self.failIf(int(mo.group('dm')) not in range(1,32)) self.failIf(mo.group('month') not in months) self.failIf(int(mo.group('hour')) not in range(0,24)) if mo.group('tz'): self.failIf(mo.group('tz') not in tz)
def _test_default_domain(enabled): self.env.config.set('notification', 'always_notify_owner', 'false') self.env.config.set('notification', 'always_notify_reporter', 'false') self.env.config.set('notification', 'smtp_always_cc', '') ticket = Ticket(self.env) ticket['cc'] = 'joenodom, [email protected]' ticket['summary'] = 'This is a summary' ticket.insert() # Be sure that at least one email address is valid, so that we # send a notification even if other addresses are not valid self.env.config.set('notification', 'smtp_always_cc', '*****@*****.**') if enabled: self.env.config.set('notification', 'smtp_default_domain', 'example.org') tn = TicketNotifyEmail(self.env) tn.notify(ticket, newticket=True) message = notifysuite.smtpd.get_message() (headers, body) = parse_smtp_message(message) # Msg should always have a 'Cc' field self.failIf('Cc' not in headers) cclist = [addr.strip() for addr in headers['Cc'].split(',')] self.failIf('*****@*****.**' not in cclist) self.failIf('*****@*****.**' not in cclist) if not enabled: self.failIf(len(cclist) != 2) self.failIf('joenodom' in cclist) else: self.failIf(len(cclist) != 3) self.failIf('*****@*****.**' not in cclist)
def _test_updater(disable): if disable: self.env.config.set('notification','always_notify_updater', 'false') ticket = Ticket(self.env) ticket['reporter'] = '*****@*****.**' ticket['summary'] = u'This is a súmmäry' ticket['cc'] = '*****@*****.**' ticket.insert() ticket['component'] = 'dummy' now = time.time() ticket.save_changes('*****@*****.**', 'This is a change', when=now) tn = TicketNotifyEmail(self.env) tn.notify(ticket, newticket=False, modtime=now) message = notifysuite.smtpd.get_message() (headers, body) = parse_smtp_message(message) # checks for header existence self.failIf(not headers) # checks for updater in the 'To' recipient list self.failIf('To' not in headers) tolist = [addr.strip() for addr in headers['To'].split(',')] if disable: self.failIf('*****@*****.**' in tolist) else: self.failIf('*****@*****.**' not in tolist)
def test_recipients(self): """To/Cc recipients""" ticket = Ticket(self.env) ticket['reporter'] = '"Joe User" <*****@*****.**>' ticket['owner'] = '*****@*****.**' ticket['cc'] = '[email protected], [email protected], ' \ '*****@*****.**' ticket['summary'] = 'Foo' ticket.insert() tn = TicketNotifyEmail(self.env) tn.notify(ticket, newticket=True) recipients = notifysuite.smtpd.get_recipients() # checks there is no duplicate in the recipient list rcpts = [] for r in recipients: self.failIf(r in rcpts) rcpts.append(r) # checks that all cc recipients have been notified cc_list = self.env.config.get('notification', 'smtp_always_cc') cc_list = "%s, %s" % (cc_list, ticket['cc']) for r in cc_list.replace(',', ' ').split(): self.failIf(r not in recipients) # checks that owner has been notified self.failIf(smtp_address(ticket['owner']) not in recipients) # checks that reporter has been notified self.failIf(smtp_address(ticket['reporter']) not in recipients)
def new_another_ticket(self, env, ticket, comment, author, trac): project_name = self.env.config.get('project','name') project_url = self.env.config.get('project','url') tkt_id = ticket.id tkt = Ticket(env) self.log.info("Ticket_id: %s" %(ticket.id)) tkt['status'] = 'new' tkt['reporter'] = project_name tkt['summary'] = '[' + project_name + '] #'+ str(tkt_id) +": "+ ticket['summary'] description = ticket['description'] if not comment == "": comment = "\n * Comment: \n" + comment + "\n" ticket_url = "\n * Ticket: " + "[" + project_url + "/ticket/" + str(tkt_id) + " " + project_name + "]" tkt['description'] = description + comment + ticket_url if ticket['taskstatus']: tkt['taskstatus'] = trac['newstatus'] self.log.info("tkt.insert") another_tkt_id = tkt.insert() if another_tkt_id: self._insert_anotherticket(env, another_tkt_id, project_name, tkt_id) self.notify(env, tkt, True) return another_tkt_id else: return False
def test_modify_missing_cnums_and_comment(self): """Editing a comments when all cnums are missing and one comment field is missing """ cursor = self.db.cursor() cursor.execute("UPDATE ticket_change SET oldvalue='' " "WHERE oldvalue='1'") cursor.execute("DELETE FROM ticket_change " "WHERE field='comment' AND oldvalue='1.2'") cursor.execute("UPDATE ticket_change SET oldvalue='' " "WHERE oldvalue='3'") self.db.commit() # Modify after missing comment ticket = Ticket(self.env, self.id) t = self.created + timedelta(seconds=50) ticket.modify_comment(self._find_change(ticket, 3), 'joe', 'New comment 3', t) self.assertChange(ticket, 3, self.t3, 'jim', keywords=dict(author='jim', old='a, b, c', new='a, b'), comment=dict(author='jim', old='', new='New comment 3'), _comment0=dict(author='joe', old='Comment 3', new=str(to_utimestamp(t)))) # Modify missing comment t = self.created + timedelta(seconds=60) ticket.modify_comment(self._find_change(ticket, 2), 'joe', 'New comment 2', t) self.assertChange(ticket, 2, self.t2, 'john', owner=dict(author='john', old='john', new='jack'), comment=dict(author='john', old='', new='New comment 2'), _comment0=dict(author='joe', old='', new=str(to_utimestamp(t))))
def change_another_ticket(self, env, tkt_id, comment, author, ticket, trac, action='another'): """ Change ticket in another_trac """ self.log.info("Call change_another_ticket(%s)" %(tkt_id)) another_proj_name = env.config.get('project','name') if not re.match(".* \["+ another_proj_name +"\]", author): (db,cursor) = self._get_dbcursor(env) utc = UTC() when = datetime.now(utc) project_name = self.env.config.get('project','name') try: tkt = Ticket(env, tkt_id, db) except util.TracError, detail: return False if action == 'native': if 'closed' in ticket['status']: cursor.execute("SELECT oldvalue,author FROM ticket_change WHERE ticket='"+tkt_id+"' AND field = 'owner' ORDER by time DESC LIMIT 1") old_owner = cursor.fetchone()[0] # tkt['owner'] = old_owner tkt['owner'] = tkt['reporter'] if tkt['taskstatus']: tkt['taskstatus'] = trac['returnstatus'] elif action == 'another': if 'closed' in tkt['status'] and 'closed' not in ticket['status']: self.log.info("TICKET FIXED AND CLOSED! REOPEN IT!") tkt['status'] = 'reopened' tkt['resolution'] = '' author = author + " [" + project_name + "]" tkt.save_changes(author, comment, when) self.notify(env, tkt, False, when) return True
def test_no_disown_from_changed_component(self): """ Verify that a ticket is not disowned when the component is changed to a non-assigned component. """ component1 = Component(self.env) component1.name = 'test1' component1.owner = 'joe' component1.insert() component2 = Component(self.env) component2.name = 'test2' component2.owner = '' component2.insert() ticket = Ticket(self.env) ticket['reporter'] = 'santa' ticket['summary'] = 'Foo' ticket['component'] = 'test1' ticket['status'] = 'new' tktid = ticket.insert() ticket = Ticket(self.env, tktid) ticket['component'] = 'test2' ticket.save_changes('jane', 'Testing') self.assertEqual('joe', ticket['owner'])
def post_process_request(self, req, template, data, content_type): if template == 'timeline.html': filter_projects = self._filtered_projects(req) if not filter_projects: #no filter means likely more than 1 project, so we insert the project name filter_projects = [project[1] for project in self.__SmpModel.get_all_projects()] if filter_projects: filtered_events = [] tickettypes = ("newticket", "editedticket", "closedticket", "attachment", "reopenedticket") self._old_render_fn = [] self._current_project = [] self._read_idx = -1 for event in data['events']: if event['kind'] in tickettypes: resource = event['kind'] == "attachment" and event['data'][0].parent or event['data'][0] if resource.realm == "ticket": ticket = Ticket( self.env, resource.id ) project = ticket.get_value_or_default('project') if project and project in filter_projects: if len(filter_projects) > 1: #only if more than 1 filtered project #store the old render function and the project to be inserted self._old_render_fn.append(event['render']) self._current_project.append(project) #redirect to our new render function (which will insert the project name) event['render'] = self._render_ticket_event #add to the list of displayed events filtered_events.append(event) else: filtered_events.append(event) data['events'] = filtered_events return template, data, content_type
def _test_default_domain(enabled): self.env.config.set("notification", "always_notify_owner", "false") self.env.config.set("notification", "always_notify_reporter", "false") self.env.config.set("notification", "smtp_always_cc", "") ticket = Ticket(self.env) ticket["cc"] = "joenodom, [email protected]" ticket["summary"] = "This is a summary" ticket.insert() # Be sure that at least one email address is valid, so that we # send a notification even if other addresses are not valid self.env.config.set("notification", "smtp_always_cc", "*****@*****.**") if enabled: self.env.config.set("notification", "smtp_default_domain", "example.org") tn = TicketNotifyEmail(self.env) tn.notify(ticket, newticket=True) message = notifysuite.smtpd.get_message() (headers, body) = parse_smtp_message(message) # Msg should always have a 'Cc' field self.failIf("Cc" not in headers) cclist = [addr.strip() for addr in headers["Cc"].split(",")] self.failIf("*****@*****.**" not in cclist) self.failIf("*****@*****.**" not in cclist) if not enabled: self.failIf(len(cclist) != 2) self.failIf("joenodom" in cclist) else: self.failIf(len(cclist) != 3) self.failIf("*****@*****.**" not in cclist)
def create(self, req, summary, description, attributes={}, notify=False): """ Create a new ticket, returning the ticket ID. PS: Borrowed from XmlRpcPlugin. """ if 'product' in attributes: env = self.env.parent or self.env if attributes['product']: env = ProductEnvironment(env, attributes['product']) else: env = self.env t = Ticket(env) t['summary'] = summary t['description'] = description t['reporter'] = req.authname for k, v in attributes.iteritems(): t[k] = v t['status'] = 'new' t['resolution'] = '' t.insert() if notify: try: tn = TicketNotifyEmail(env) tn.notify(t, newticket=True) except Exception, e: self.log.exception("Failure sending notification on creation " "of ticket #%s: %s" % (t.id, e))
def test_email_map(self): """Login-to-email mapping""" self.env.config.set("notification", "always_notify_owner", "true") self.env.config.set("notification", "always_notify_reporter", "true") self.env.config.set("notification", "smtp_always_cc", "*****@*****.**") self.env.known_users = [ ("joeuser", "Joe User", "*****@*****.**"), ("jim@domain", "Jim User", "*****@*****.**"), ] ticket = Ticket(self.env) ticket["reporter"] = "joeuser" ticket["owner"] = "jim@domain" ticket["summary"] = "This is a summary" ticket.insert() tn = TicketNotifyEmail(self.env) tn.notify(ticket, newticket=True) message = notifysuite.smtpd.get_message() (headers, body) = parse_smtp_message(message) # Msg should always have a 'To' field self.failIf("To" not in headers) tolist = [addr.strip() for addr in headers["To"].split(",")] # 'To' list should have been resolved to the real email address self.failIf("*****@*****.**" not in tolist) self.failIf("*****@*****.**" not in tolist) self.failIf("joeuser" in tolist) self.failIf("jim@domain" in tolist)
def add_tickets(self, project, customerrequest, tickets, reporter, notify=False): from trac.ticket.notification import TicketNotifyEmail from trac.util.text import exception_to_unicode from penelope.core.models.dashboard import User settings = get_current_registry().settings tracenvs = settings.get('penelope.trac.envs') request = get_current_request() for trac in project.tracs: for t in tickets: owner = DBSession.query(User).get(t['owner']) ticket = {'summary': t['summary'], 'description': t['description'], 'customerrequest': customerrequest.id, 'reporter': reporter.email, 'type': 'task', 'priority': 'major', 'milestone': 'Backlog', 'owner': owner.email, 'status': 'new'} tracenv = Environment('%s/%s' % (tracenvs, trac.trac_name)) tracenv.abs_href.base = trac.api_uri t = Ticket(tracenv) t.populate(ticket) t.insert() if notify: try: tn = TicketNotifyEmail(tracenv) tn.notify(t, newticket=True) except Exception, e: request.add_message('Failure sending notification on creation ' 'of a ticket #%s: %s' % (t.id, exception_to_unicode(e)), 'error')
def test_ignore_domains(self): """Non-SMTP domain exclusion""" self.env.config.set("notification", "ignore_domains", "example.com, example.org") self.env.known_users = [ ("*****@*****.**", "No Email", ""), ("*****@*****.**", "With Email", "*****@*****.**"), ] ticket = Ticket(self.env) ticket["reporter"] = "*****@*****.**" ticket["owner"] = "*****@*****.**" ticket["summary"] = "This is a summary" ticket.insert() tn = TicketNotifyEmail(self.env) tn.notify(ticket, newticket=True) message = notifysuite.smtpd.get_message() (headers, body) = parse_smtp_message(message) # Msg should always have a 'To' field self.failIf("To" not in headers) tolist = [addr.strip() for addr in headers["To"].split(",")] # 'To' list should not contain addresses with non-SMTP domains self.failIf("*****@*****.**" in tolist) self.failIf("*****@*****.**" in tolist) # 'To' list should have been resolved to the actual email address self.failIf("*****@*****.**" not in tolist) self.failIf(len(tolist) != 1)
def _insert_ticket(self, summary, **kw): """Helper for inserting a ticket into the database""" ticket = Ticket(self.env) ticket['summary'] = summary for k, v in kw.items(): ticket[k] = v return ticket.insert()
def _insert_ticket(self, estimation, owner): ticket = Ticket(self.env) ticket['summary'] = 'Test Ticket' ticket['owner'] = owner ticket['hours_remaining'] = estimation ticket['milestone'] = 'milestone1' return ticket.insert()
def _save_changes(self, changes, author): """Save ticket changes.""" if self.audit in ('log','none'): db = self.env.get_db_cnx() cursor = db.cursor() for id,(field,new_pos) in changes.items(): cursor.execute(""" SELECT value from ticket_custom WHERE name=%s AND ticket=%s """, (field,id)) result = cursor.fetchone() if result: old_pos = result[0] cursor.execute(""" UPDATE ticket_custom SET value=%s WHERE name=%s AND ticket=%s """, (new_pos,field,id)) else: old_pos = '(none)' cursor.execute(""" INSERT INTO ticket_custom (ticket,name,value) VALUES (%s,%s,%s) """, (id,field,new_pos)) if self.audit == 'log': self.log.info("%s reordered ticket #%s's %s from %s to %s" \ % (author,id,field,old_pos,new_pos)) db.commit() else: for id,(field,new_pos) in changes.items(): ticket = Ticket(self.env, id) ticket[field] = new_pos ticket.save_changes(author=author, comment='')
def test_props_format_wrap_bothsides_unicode(self): self.env.config.set("notification", "mime_encoding", "none") self.env.config.set("notification", "ambiguous_char_width", "double") ticket = Ticket(self.env) ticket["summary"] = u"This is a summary" ticket["reporter"] = u"anonymous" ticket["status"] = u"new" ticket["owner"] = u"somebody" ticket["type"] = u"defect" ticket["priority"] = u"major" ticket["milestone"] = u"Trac 在经过修改的BSD协议下发布。" u"[1:]协议的完整文本可以[2:在线查" u"看]也可在发布版的 [3:COPYING] 文" u"件中找到。" ticket["component"] = ( u"Trac は BSD ライセンスのもとで配" u"布されています。[1:]このライセ" u"ンスの全文は、𠀋配布ファイル" u"に含まれている[3:CОPYING]ファイ" u"ルと同じものが[2:オンライン]で" u"参照できます。" ) ticket["version"] = u"2.0" ticket["resolution"] = u"fixed" ticket["keywords"] = u"" ticket.insert() formatted = """\ Reporter: anonymous | Owner: somebody Type: defect | Status: new Priority: major | Milestone: Trac 在经过修改的BSD协 Component: Trac は BSD ライセンス | 议下发布。[1:]协议的完整文本可以[2: のもとで配布されています。[1:]こ | 在线查看]也可在发布版的 [3:COPYING] のライセンスの全文は、𠀋配布ファ | 文件中找到。 イルに含まれている[3:CОPYING]フ | Version: 2.0 ァイルと同じものが[2:オンライン] | Keywords: で参照できます。 | Resolution: fixed |""" self._validate_props_format(formatted, ticket)
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 = Ticket(self.env) ticket.populate({ 'reporter': 'reporter1', 'summary': 'the summary', 'component': 'component3', 'owner': 'cowner3', 'status': 'new', }) tkt_id = ticket.insert() req = self._create_request(method='POST', args={ 'id': tkt_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, tkt_id) self.assertEqual('component4', ticket['component']) self.assertEqual('cowner4', ticket['owner'])
def test_set_field_stripped(self): """ Verify that whitespace around ticket fields is stripped, except for textarea fields. """ ticket = Ticket(self.env) ticket['component'] = ' foo ' ticket['description'] = ' bar ' self.assertEqual('foo', ticket['component']) self.assertEqual(' bar ', ticket['description'])
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 post_process_request(self, req, template, data, content_type): if req.path_info.startswith('/ticket/'): # In case of an invalid ticket, the data is invalid if not data: return template, data, content_type tkt = data['ticket'] links = TicketLinks(self.env, tkt) for i in links.blocked_by: if Ticket(self.env, i)['status'] != 'closed': add_script(req, 'mastertickets/js/disable_resolve.js') break # Add link to depgraph if needed if links: add_ctxtnav(req, 'Depgraph', req.href.depgraph('ticket', tkt.id)) for change in data.get('changes', {}): if not 'fields' in change: continue for field, field_data in change['fields'].iteritems(): if field in self.fields: if field_data['new'].strip(): new = set([int(n) for n in field_data['new'].split(',')]) else: new = set() if field_data['old'].strip(): old = set([int(n) for n in field_data['old'].split(',')]) else: old = set() add = new - old sub = old - new elms = tag() if add: elms.append( tag.em(u', '.join([unicode(n) for n in sorted(add)])) ) elms.append(u' added') if add and sub: elms.append(u'; ') if sub: elms.append( tag.em(u', '.join([unicode(n) for n in sorted(sub)])) ) elms.append(u' removed') field_data['rendered'] = elms #add a link to generate a dependency graph for all the tickets in the milestone if req.path_info.startswith('/milestone/'): if not data: return template, data, content_type milestone = data['milestone'] add_ctxtnav(req, 'Depgraph', req.href.depgraph('milestone', milestone.name)) return template, data, content_type
def _update_tickets(self, changeset, review, status_changed): """Updates the tickets referenced by the given review's changeset with a comment of field changes. Field changes and command execution may occur if specified in trac.ini and the review's changeset is the last one of the ticket.""" status = review.encode(review.status).lower() # build comment comment = None if status_changed or review['summary']: if status_changed: comment = "Code review set to %s" % review['status'] else: comment = "Code review comment" repos = changeset.repos ref = review.changeset disp_ref = str(repos.short_rev(review.changeset)) if review.repo: ref += '/' + review.repo disp_ref += '/' + review.repo comment += ' for [changeset:"%s" %s]' % (ref, disp_ref) if review['summary']: comment += ":\n\n%s" % review['summary'] invoked = False for ticket in review.tickets: tkt = Ticket(self.env, ticket) # determine ticket changes changes = {} if self._is_complete(ticket, review, failed_ok=True): changes = self._get_ticket_changes(tkt, status) # update ticket if there's a review summary or ticket changes if comment or changes: for field, value in changes.items(): tkt[field] = value tkt.save_changes(review['reviewer'], comment) # check to invoke command if not invoked and self._is_complete(ticket, review): self._execute_command() invoked = True
def validate_ticket(self, req, ticket): tid = ticket.id links = self._prepare_links(ticket) if req.args.get('action') == 'resolve' and \ req.args.get('action_resolve_resolve_resolution') == 'fixed': for i in links.blocked_by: if Ticket(self.env, i)['status'] != 'closed': yield None, "Ticket #%s is blocking this ticket" % i # Check that ticket does not have itself as a blocker if tid in links.blocking | links.blocked_by: yield 'blocked_by', "This ticket is blocking itself" return # Check that there aren't any blocked_by in blocking or their parents blocking = links.blocking.copy() while len(blocking) > 0: if len(links.blocked_by & blocking) > 0: yield 'blocked_by', "This ticket has circular dependencies" return new_blocking = set() for link in blocking: tmp_tkt = Ticket(self.env, link) new_blocking |= TicketLinks(self.env, tmp_tkt).blocking blocking = new_blocking for field in ('blocking', 'blockedby'): try: ids = self.NUMBERS_RE.findall(ticket[field] or '') for tid in ids[:]: for _ in self.env.db_query(""" SELECT id FROM ticket WHERE id=%s """, (tid,)): break else: ids.remove(tid) ticket[field] = ', '.join(sorted(ids, key=lambda x: int(x))) except Exception, e: self.log.debug("MasterTickets: Error parsing %s \"%s\": %s", field, ticket[field], e) yield field, "Not a valid list of ticket IDs"
def test_can_save_ticket_without_explicit_username(self): ticket = Ticket(self.env) ticket.insert() ticket['summary'] = 'another summary' ticket.save_changes() for change in ticket.get_changelog(): self.assertIsNone(change[1])
def test_delete_mid_comment_inconsistent(self): # Make oldvalue on keywords for change 4 inconsistent. This should # result in no change in oldvalue when deleting change 3. The # oldvalue of foo should change normally. self.env.db_transaction(""" UPDATE ticket_change SET oldvalue='1, 2' WHERE field='keywords' AND oldvalue='a, b' """) ticket = Ticket(self.env, self.id) self.assertChange(ticket, 4, self.t4, 'joe', comment=dict(author='joe', old='4', new='Comment 4'), keywords=dict(author='joe', old='1, 2', new='a'), foo=dict(author='joe', old='change3', new='change4')) ticket.delete_change(3) self.assertIsNone(ticket.get_change(3)) self.assertEqual('a', ticket['keywords']) self.assertChange(ticket, 4, self.t4, 'joe', comment=dict(author='joe', old='4', new='Comment 4'), keywords=dict(author='joe', old='1, 2', new='a'), foo=dict(author='joe', old='change2', new='change4'))
def test_modify_comment(self): """Check modification of a "standalone" comment""" ticket = Ticket(self.env, self.id) self.assertChange(ticket, 1, self.t1, 'jack', comment=dict(author='jack', old='1', new='Comment 1')) self.assertChange(ticket, 2, self.t2, 'john', owner=dict(author='john', old='john', new='jack'), comment=dict(author='john', old='1.2', new='Comment 2')) self.assertChange(ticket, 3, self.t3, 'jim', keywords=dict(author='jim', old='a, b, c', new='a, b'), comment=dict(author='jim', old='3', new='Comment 3')) t = self.created + timedelta(seconds=10) ticket.modify_comment(self._find_change(ticket, 1), 'joe', 'New comment 1', t) self.assertChange(ticket, 1, self.t1, 'jack', comment=dict(author='jack', old='1', new='New comment 1'), _comment0=dict(author='joe', old='Comment 1', new=str(to_utimestamp(t)))) self.assertEqual(t, Ticket(self.env, self.id)['changetime'])
def _get_changelog_comments(self, tid): changelog = Ticket(self.env, tkt_id=tid).get_changelog() changelog_str = '' for date, author, field, old, new, permanent in changelog: if field == 'comment' and new != None and new != '' and old != None and old != '': changelog_str = changelog_str + ('comment:%s %s %s\n' % ( old, author or '', self.formats['datetime'].restore(date))) changelog_str = changelog_str + new + '\n\n' return changelog_str
def test_remove_tags(self): resource = Resource('ticket', 1) # Anonymous lacks required permissions. self.assertRaises(PermissionError, self.provider.remove_resource_tags, self.req, resource) self.req.authname = 'user' self.req.perm = PermissionCache(self.env, username='******') # Shouldn't raise an error with appropriate permission. self.provider.remove_resource_tags(self.req, resource, 'comment') tkt = Ticket(self.env, 1) self.assertEquals(tkt['keywords'], '')
def test_changelog(self): tkt_id = self._insert_ticket('Test', reporter='joe', component='foo', milestone='bar') ticket = Ticket(self.env, tkt_id) ticket['component'] = 'bar' ticket['milestone'] = 'foo' ticket.save_changes('jane', 'Testing', when=42) for t, author, field, old, new in ticket.get_changelog(): self.assertEqual((42, 'jane'), (t, author)) if field == 'component': self.assertEqual(('foo', 'bar'), (old, new)) elif field == 'milestone': self.assertEqual(('bar', 'foo'), (old, new)) elif field == 'comment': self.assertEqual(('', 'Testing'), (old, new)) else: self.fail('Unexpected change (%s)' % ((t, author, field, old, new), ))
def test_modify_missing_comment(self): """Editing a comment where the comment field is missing""" self.env.db_transaction(""" DELETE FROM ticket_change WHERE field='comment' AND oldvalue='1.2' """) ticket = Ticket(self.env, self.id) t = self.created + timedelta(seconds=40) ticket.modify_comment(self._find_change(ticket, 2), 'joe', 'New comment 2', t) self.assertChange(ticket, 2, self.t2, 'john', owner=dict(author='john', old='john', new='jack'), comment=dict(author='john', old='', new='New comment 2'), _comment0=dict(author='joe', old='', new=str(to_utimestamp(t))))
def test_delete_mid_comment_by_date(self): ticket = Ticket(self.env, self.id) self.assertChange(ticket, 4, self.t4, 'joe', comment=dict(author='joe', old='4', new='Comment 4'), keywords=dict(author='joe', old='a, b', new='a'), foo=dict(author='joe', old='change3', new='change4')) ticket.delete_change(cdate=self.t3) self.assertEqual(None, ticket.get_change(cdate=self.t3)) self.assertEqual('a', ticket['keywords']) self.assertChange(ticket, 4, self.t4, 'joe', comment=dict(author='joe', old='4', new='Comment 4'), keywords=dict(author='joe', old='a, b, c', new='a'), foo=dict(author='joe', old='change2', new='change4')) self.assertEqual(self.t4, ticket.time_changed)
def get_resource_description(self, resource, format=None, context=None, **kwargs): if format == 'compact': return '#%s' % resource.id elif format == 'summary': from trac.ticket.model import Ticket ticket = Ticket(self.env, resource.id) args = [ticket[f] for f in ('summary', 'status', 'resolution', 'type')] return self.format_summary(*args) return _("Ticket #%(shortname)s", shortname=resource.id)
def test_set_owner_to_field(self): self.env.config.set( 'ticket', 'workflow', 'ConfigurableTicketWorkflow,TicketWorkflowOpOwnerField') self._config_set('ticket-workflow', [ ('to-owner', '* -> assigned'), ('to-owner.operations', 'set_owner_to_field'), ('to-owner.set_owner_to_field', 'keywords'), ]) tktid = self._insert_ticket(summary='set owner to field', reporter='anonymous', owner='joe', keywords='john') ticket = Ticket(self.env, tktid) req = self._post_req('to-owner', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('john', ticket['owner']) self.assertEqual('assigned', ticket['status'])
def _get_tickets(self, oldstate, days): db = self.env.get_db_cnx() timecutoff = (int(time.time()) - float(days) * 86400) * 1000000 cursor = db.cursor() cursor.execute( ''' SELECT id FROM ticket LEFT JOIN ticket_change ON (id = ticket AND field = %s) WHERE ticket.status = %s GROUP BY ticket.id HAVING MAX(ticket_change.time) <= %s OR (MAX(ticket_change.time) IS NULL AND MAX(ticket.time) <= %s) ORDER BY id ''', ('status', oldstate, timecutoff, timecutoff)) for (a, ) in cursor: yield Ticket(self.env, a)
def _comment_ticket(self, req, tkt_id): tkt = Ticket(self.env, tkt_id) if not tkt.exists: raise ResourceNotFound('Ticket %s does not exist.' % tkt_id, 'Invalid Ticket Id') req.perm.require('TICKET_MODIFY', Resource('ticket', tkt.resource)) changes = TicketModule(self.env).rendered_changelog_entries(req, tkt) return 'itteco_ticket_comment.html', { 'ticket': tkt, 'changes': changes }, 'text/html'
def test_remove_tags(self): req = MockRequest(self.env, authname='editor') anon_req = MockRequest(self.env, authname='anonymous') resource = Resource('ticket', 1) # Anonymous lacks required permissions. self.assertRaises(PermissionError, self.provider.remove_resource_tags, anon_req, resource) # Shouldn't raise an error with appropriate permission. self.provider.remove_resource_tags(req, resource, 'comment') ticket = Ticket(self.env, 1) self.assertEquals(ticket['keywords'], '')
def describe_tagged_resource(self, req, resource): if not self.check_permission(req.perm, 'view'): return '' ticket = Ticket(self.env, resource.id) if ticket.exists: # Use the corresponding IResourceManager. ticket_system = TicketSystem(self.env) return ticket_system.get_resource_description(ticket.resource, format='summary') else: return ''
def test_set_owner_to_component_owner(self): self.env.config.set( 'ticket', 'workflow', 'ConfigurableTicketWorkflow,TicketWorkflowOpOwnerComponent') self._config_set('ticket-workflow', [ ('to-c-owner', '* -> assigned'), ('to-c-owner.operations', 'set_owner_to_component_owner'), ]) self._insert_component('component3', 'foo') tktid = self._insert_ticket(summary='set owner to component owner', reporter='anonymous', owner='joe', component='component3') ticket = Ticket(self.env, tktid) req = self._post_req('to-c-owner', ticket) self.assertTrue(self.tktmod.match_request(req)) self.assertRaises(RequestDone, self.tktmod.process_request, req) ticket = Ticket(self.env, tktid) self.assertEqual('foo', ticket['owner']) self.assertEqual('assigned', ticket['status'])
def _setup_env_and_req(self, max_size, field_value): self.env.config.set('ticket-custom', 'text1', 'text') self.env.config.set('ticket-custom', 'text1.max_size', max_size) ticket = insert_ticket(self.env, summary='summary', text1='init') change_time = Ticket(self.env, ticket.id)['changetime'] view_time = str(to_utimestamp(change_time)) req = MockRequest( self.env, method='POST', path_info='/ticket/%d' % ticket.id, args={'submit': 'Submit changes', 'field_text1': field_value, 'action': 'leave', 'view_time': view_time}) return req
def test_populate_ticket(self): data = {'summary': 'Hello world', 'reporter': 'john', 'foo': 'bar', 'checkbox_cbon': '', 'cbon': 'on', 'checkbox_cboff': ''} ticket = Ticket(self.env) ticket.populate(data) # Standard fields self.assertEqual('Hello world', ticket['summary']) self.assertEqual('john', ticket['reporter']) # An unknown field self.assertIsNone(ticket['bar']) # Custom field self.assertEqual('bar', ticket['foo']) # Custom field of type 'checkbox' self.assertEqual('on', ticket['cbon']) self.assertEqual('0', ticket['cboff'])
def _assign_milestone(self, req): ticket_id = int(req.args.get('ticket_id')) milestone = req.args.get('milestone') author = get_reporter_id(req, 'author') if milestone == '(unscheduled)': milestone = '' to_result = {} ticket = None try: ticket = Ticket(self.env, ticket_id) except: to_result['msg'] = "Couldn't find ticket!" if ticket: db = self.env.get_db_cnx() cursor = db.cursor() try: ticket['milestone'] = milestone ticket.save_changes(author, "") to_result['num_tickets'] = self._get_num_tickets( cursor, milestone) except: to_result['msg'] = "Unable to assign milestone" data = simplejson.dumps(to_result) if 'msg' in to_result: print "error:", to_result['msg'] req.send_response(202) else: req.send_response(200) req.send_header('Content-Type', 'application/json') req.end_headers() req.write(data)
def post_process_request(self, req, template, data, content_type): if template == 'timeline.html': filter_projects = self._filtered_projects(req) if not filter_projects: #no filter means likely more than 1 project, so we insert the project name filter_projects = [ project[1] for project in self.__SmpModel.get_all_projects() ] if filter_projects: filtered_events = [] tickettypes = ("newticket", "editedticket", "closedticket", "attachment", "reopenedticket") self._old_render_fn = [] self._current_project = [] self._read_idx = -1 for event in data['events']: if event['kind'] in tickettypes: resource = event['kind'] == "attachment" and event[ 'data'][0].parent or event['data'][0] if resource.realm == "ticket": ticket = Ticket(self.env, resource.id) project = ticket.get_value_or_default('project') if project and project in filter_projects: if len( filter_projects ) > 1: #only if more than 1 filtered project #store the old render function and the project to be inserted self._old_render_fn.append(event['render']) self._current_project.append(project) #redirect to our new render function (which will insert the project name) event['render'] = self._render_ticket_event #add to the list of displayed events filtered_events.append(event) else: filtered_events.append(event) data['events'] = filtered_events return template, data, content_type
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 = Ticket(self.env) self.ticket['status'] = 'new' self.ticket.insert() with self.env.db_transaction as db: for user in ('user1', 'user2', 'user3', 'user4'): db("INSERT INTO session VALUES (%s, %s, %s)", (user, 1, 0)) 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 = """\
def test_action_with_side_effects(self): """Actions can have operations with side effects.""" self.env.config.set('ticket-workflow', 'buckify', '* -> *') self.env.config.set('ticket-workflow', 'buckify.operations', 'set_owner') self.req.args = {} self.req.args['action_buckify_reassign_owner'] = 'buck' first_ticket_id = self._insert_ticket('Test 1', reporter='joe', owner='foo') second_ticket_id = self._insert_ticket('Test 2', reporter='joe') selected_tickets = [first_ticket_id, second_ticket_id] batch = BatchModifyModule(self.env) batch._save_ticket_changes(self.req, selected_tickets, {}, '', 'buckify') ticket = Ticket(self.env, int(first_ticket_id)) changes = ticket.get_changelog() self.assertFieldChanged(first_ticket_id, 'owner', 'buck') self.assertFieldChanged(second_ticket_id, 'owner', 'buck')
def check_permission(self, action, username, resource, perm): if action in self.allowed_actions and \ resource is not None and \ resource.realm == 'ticket' and \ resource.id is not None: try: ticket = Ticket(self.env, resource.id) except ResourceNotFound: pass else: if ticket['reporter'] == username: return True
def test_modify_missing_cnum(self): """Editing a comment with no cnum in oldvalue""" self.env.db_transaction( "UPDATE ticket_change SET oldvalue='' WHERE oldvalue='3'") ticket = Ticket(self.env, self.id) t = self.created + timedelta(seconds=30) ticket.modify_comment(self._find_change(ticket, 3), 'joe', 'New comment 3', t) self.assertChange(ticket, 3, self.t3, 'jim', keywords=dict(author='jim', old='a, b, c', new='a, b'), comment=dict(author='jim', old='', new='New comment 3'), _comment0=dict(author='joe', old='Comment 3', new=str(to_utimestamp(t))))
def _reindex_ticket(self, realm, feedback, finish_fb): db = self.env.get_read_db() cursor = db.cursor() cursor.execute("SELECT id FROM ticket") def check(ticket, status): return (status is None or ticket.values['changetime'] > to_datetime(int(status))) resources = (Ticket(self.env, tkt_id) for (tkt_id, ) in cursor) index = self.ticket_created return self._index(realm, resources, check, index, feedback, finish_fb)
def test_ticket_custom_field_max_size_is_zero(self): """Validation is skipped when max_size attribute is <= 0.""" max_size = 0 field_value = 'a' * 100 req = self._setup_env_and_req(max_size, field_value) self.assertTrue(self.ticket_module.match_request(req)) with self.assertRaises(RequestDone): self.ticket_module.process_request(req) self.assertEqual(0, len(req.chrome['warnings'])) self.assertEqual(field_value, Ticket(self.env, 1)['text1'])