# -*- coding: iso-8859-1 -* ## ## ## import unittest import re from pprint import pprint from time import time from random import randint import sys, os if __name__ == '__main__': execfile(os.path.join(sys.path[0], 'framework.py')) from Globals import SOFTWARE_HOME from Testing import ZopeTestCase from AccessControl import getSecurityManager from AccessControl.SecurityManagement import newSecurityManager, noSecurityManager from DateTime import DateTime from zExceptions import Unauthorized import Acquisition from base import TestBase #ZopeTestCase.installProduct('MailHost') #ZopeTestCase.installProduct('ZCatalog') #ZopeTestCase.installProduct('ZCTextIndex') #ZopeTestCase.installProduct('SiteErrorLog') #ZopeTestCase.installProduct('PythonScripts') #ZopeTestCase.installProduct('IssueTrackerProduct') from Products.IssueTrackerProduct.Permissions import IssueTrackerManagerRole, IssueTrackerUserRole from Products.IssueTrackerProduct.Constants import ISSUEUSERFOLDER_METATYPE, \ DEBUG, ISSUE_DRAFT_METATYPE, TEMPFOLDER_REQUEST_KEY, \ FILTERVALUEFOLDER_THRESHOLD_CLEANING, FILTEROPTION_METATYPE from Products.IssueTrackerProduct.Errors import DataSubmitError, UserSubmitError #------------------------------------------------------------------------------ # # Some constants # #------------------------------------------------------------------------------ pre_submitissue_script_src = """ ## Script (Python) "pre_SubmitIssue" ##parameters= ##title= ## request = context.REQUEST title = request.get('title',u'') if title.lower().startswith(u'a'): return {'title':u'Subject line must NOT start with an A'} """ post_submitissue_script_src = """ ## Script (Python) "post_SubmitIssue" ##parameters=issue ##title= ## if 'Security' in issue.getSections(): # increase the urgency by one notch urgency = issue.getUrgency() options = issue.getUrgencyOptions() # acquired try: urgency = options[options.index(urgency)+1] except IndexError: # was already at the topmost return issue.editIssueDetails(urgency=urgency) """ #------------------------------------------------------------------------------ class NewFileUpload: def __init__(self, file_path): self.file = open(file_path) self.filename = os.path.basename(file_path) self.file_path = file_path def read(self, bytes=None): if bytes: return self.file.read(bytes) else: return self.file.read() def seek(self, bytes, mode=0): self.file.seek(bytes, mode) def tell(self): return self.file.tell() class DodgyNewFileUpload: """ a file upload that returns a blank string no when you read it """ def __init__(self, file_path): self.file = open(file_path) self.filename = os.path.basename(file_path) self.file_path = file_path def read(self, bytes=None, mode=0): return "" from Products.IssueTrackerProduct.IssueTracker import FilterValuer #------------------------------------------------------------------------------ class TestBasex(ZopeTestCase.ZopeTestCase): def dummy_redirect(self, *a, **kw): self.has_redirected = a[0] if kw: print "*** Redirecting to %r + (%s)" % (a[0], kw) else: print "*** Redirecting to %r" % a[0] def afterSetUp(self): # install an issue tracker dispatcher = self.folder.manage_addProduct['IssueTrackerProduct'] dispatcher.manage_addIssueTracker('tracker', 'Issue Tracker') # install an error_log #dispatcher = self.folder.manage_addProduct['SiteErrorLog'] #dispatcher.manage_addErrorLog() # install a MailHost if not DEBUG: dispatcher = self.folder.manage_addProduct['MailHost'] dispatcher.manage_addMailHost('MailHost') # if you set this override you won't be able to do a transaction.get().commit() # in the unit tests. #self.mexpenses.http_redirect = self.dummy_redirect request = self.app.REQUEST sdm = self.app.session_data_manager request.set('SESSION', sdm.getSessionData()) #self.has_redirected = False def set_cookie(self, key, value, expires=365, path='/', across_domain_cookie_=False, **kw): self.app.REQUEST.cookies[key] = value #def afterClear(self): # global __trapped_emails__ # __trapped_emails__ = [] try: from zope import traversing, component, interface except ImportError: traversing = None TestFunctionalBase = None if traversing: from zope.traversing.adapters import DefaultTraversable from zope.traversing.interfaces import ITraversable from zope.component import provideAdapter from zope import interface from zope.interface import implements class TestFunctionalBase(ZopeTestCase.FunctionalTestCase, TestBase): def afterSetUp(self): # install an issue tracker dispatcher = self.folder.manage_addProduct['IssueTrackerProduct'] dispatcher.manage_addIssueTracker('tracker', 'Issue Tracker') # install a MailHost if not DEBUG: dispatcher = self.folder.manage_addProduct['MailHost'] dispatcher.manage_addMailHost('MailHost') request = self.app.REQUEST sdm = self.app.session_data_manager request.set('SESSION', sdm.getSessionData()) def beforeSetUp(self): super(ZopeTestCase.FunctionalTestCase, self).beforeSetUp() component.provideAdapter( \ traversing.adapters.DefaultTraversable, (interface.Interface,),ITraversable) class IssueTrackerTestCase(TestBase): try: # For zope 2.10+ to make it shut up about from zope.interface import implements from zope.traversing.interfaces import ITraversable from zope.traversing.adapters import DefaultTraversable from zope import interface from zope.component import provideAdapter implements(ITraversable) provideAdapter(DefaultTraversable, (interface.Interface,),ITraversable) except ImportError: pass def test_addingIssue(self): """ test something """ #self.tracker = self.folder['mexpenses'] tracker = self.folder.tracker request = self.app.REQUEST request.set('title', u'TITLE') request.set('fromname', u'From name') request.set('email', u'email@address.com') request.set('description', u'DESCRIPTION') request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) tracker.SubmitIssue(request) self.assertEqual(len(tracker.getIssueObjects()), 1) def test_modifyingIssue(self): """ test something """ #self.tracker = self.folder['mexpenses'] tracker = self.folder.tracker request = self.app.REQUEST request.set('title', u'TITLE') request.set('fromname', u'From name') request.set('email', u'email@address.com') request.set('description', u'DESCRIPTION') request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) tracker.SubmitIssue(request) issue = tracker.getIssueObjects()[0] request.set('comment', u'COMMENT') issue.ModifyIssue(request) self.assertEqual(len(issue.getThreadObjects()), 1) def test_debatingIssue(self): """ test posting a followup under a different email address than the original """ tracker = self.folder.tracker tracker.sitemaster_email = 'something@valid.com' request = self.app.REQUEST request.set('title', u'TITLE') request.set('fromname', u'From name') request.set('email', u'email@address.com') request.set('description', u'DESCRIPTION') request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) tracker.SubmitIssue(request) issue = tracker.getIssueObjects()[0] request.set('comment', u'COMMENT') request.set('fromname', u'Someone Else') request.set('email', u'else@address.com') request.set('notify', 1) issue.ModifyIssue(request) # there should now be a notifiation object self.assertEqual(len(issue.getCreatedNotifications()), 1) # have a look at that notification object notification = issue.getCreatedNotifications()[0] self.assertEqual(notification.getTitle(), u'TITLE') self.assertTrue(isinstance(notification.getTitle(), unicode)) self.assertEqual(notification.getIssueID(), issue.getId()) self.assertEqual(notification.getEmails(), [u'email@address.com']) if tracker.doDispatchOnSubmit(): self.assertTrue(notification.isDispatched()) # we should now expect an email to have been sent to email@address.com assert self.snatched_emails, "not trapped emails" latest_email = self.snatched_emails[-1] self.assertTrue(latest_email['to'].find('email@address.com') > -1) self.assertTrue(latest_email['fr'].find('something@valid.com') > -1) def test_debatingIssue_withSmartAvoidanceOfNotifications(self): """ If A posts an issue, B follows up and shortly there after A follows up too, then if the automatic dispatcher is switched off, the notification to A can be ignored since A has already seen the followup. """ tracker = self.folder.tracker # Important tracker.dispatch_on_submit = False A = u'email@address.com' Af = u'From name' request = self.app.REQUEST request.set('title', u'TITLE') request.set('fromname', Af) request.set('email', A) request.set('description', u'DESCRIPTION') request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) tracker.SubmitIssue(request) B = u'else@address.com' Bf = u'Someone Else' issue = tracker.getIssueObjects()[0] request.set('comment', u'COMMENT') request.set('fromname', Bf) request.set('email', B) request.set('notify', 1) issue.ModifyIssue(request) # have a look at that notification object notification = issue.getCreatedNotifications()[0] self.assertFalse(notification.isDispatched()) # A returns and posts a followup request.set('comment', u'REPLY') request.set('fromname', Af) request.set('email', A) request.set('notify', 1) issue.ModifyIssue(request) # Since the second followup done by A must mean that A # doesn't need to be notified about the first notification # since A has already made a newer followup. # However, B's notification should still be there. self.assertEqual(len(issue.getCreatedNotifications()), 1) notification = issue.getCreatedNotifications()[0] # this should be designated to B self.assertEqual(notification.getEmails(), [B]) # Ok, let's do it again. # Now, C joins in so that each notification is designated # to two people. By adding a followup by A or B, there is # no need to send out the notification to A or B. C = u'C@email.com' Cf = u'Mr. C' request.set('comment', u'COMMENT BY C') request.set('fromname', Cf) request.set('email', C) request.set('notify', 1) issue.ModifyIssue(request) # there should now be one new notification designated # to A AND B. self.assertEqual(len(issue.getCreatedNotifications()), 2) # let's look at the latest notification notification = issue.getCreatedNotifications(sort=True)[1] self.assertEqual(notification.getEmails(), [A, B]) request.set('comment', u'COMMENT BY B again') request.set('fromname', Bf) request.set('email', B) request.set('notify', 1) issue.ModifyIssue(request) self.assertEqual(len(issue.getCreatedNotifications()), 2) # let's look at the latest notification notification = issue.getCreatedNotifications(sort=True)[1] self.assertEqual(notification.getEmails(), [A, C]) def test_debatingIssue_withSmartAvoidanceOfNotifications_part2(self): """ same test_debatingIssue_withSmartAvoidanceOfNotifications() but this time test what happens if an issue is created with always notify on or an issue is assigned to someone. """ tracker = self.folder.tracker # Important tracker.dispatch_on_submit = False # add an issue user folder # Since manage_addIssueUserFolder() needs to add the two extra roles # we have to do that first because manage_addIssueUserFolder() isn't # allowed to it because it's not a POST request. tracker._addRole(IssueTrackerUserRole) tracker._addRole(IssueTrackerManagerRole) tracker.manage_addProduct['IssueTrackerProduct']\ .manage_addIssueUserFolder(keep_usernames=True) tracker.acl_users.userFolderAddUser("user", "secret", [IssueTrackerUserRole], [], email="user@test.com", fullname="User Name") # make the always notify of issuetracker be 'user' and A A = 'a@a.com' Af = 'Aaa' checked = [] for each in (A, 'user'): valid, better_spelling = tracker._checkAlwaysNotify(each) if valid: checked.append(better_spelling) tracker.always_notify = checked # If someone else now adds an issue, a notification should # be made going out to user@test.com adn a@a.com B = u'email@address.com' Bf = u'From name' request = self.app.REQUEST request.set('title', u'TITLE') request.set('fromname', Bf) request.set('email', B) request.set('description', u'DESCRIPTION') request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) tracker.SubmitIssue(request) issue = tracker.getIssueObjects()[0] # have a look at that notification object self.assertEqual(len(issue.getCreatedNotifications()), 1) notification = issue.getCreatedNotifications()[0] self.assertFalse(notification.isDispatched()) self.assertEqual(notification.getEmails(), ['a@a.com','user@test.com']) # now, if a@a.com follows up on B's new issue, there'll be # you can cross off a@a.com from the notification # object. request.set('comment', u'COMMENT') request.set('fromname', Af) request.set('email', A) request.set('notify', 1) issue.ModifyIssue(request) # there should now be a new notification object where # the latest one goes out to the submitter of the issue self.assertEqual(len(issue.getCreatedNotifications()), 2) latest_notification = issue.getCreatedNotifications(sort=True)[-1] self.assertEqual(latest_notification.getEmails(), [B]) def test_debatingIssue_withSmartAvoidanceOfNotifications_part3(self): """ same as test_debatingIssue_withSmartAvoidanceOfNotifications() but create an assignment and then later as that assignee, participate in the issue and that should cancel the notification going out to the assignee. """ tracker = self.folder.tracker # Important tracker.dispatch_on_submit = False # add an issue user folder # Since manage_addIssueUserFolder() needs to add the two extra roles # we have to do that first because manage_addIssueUserFolder() isn't # allowed to it because it's not a POST request. tracker._addRole(IssueTrackerUserRole) tracker._addRole(IssueTrackerManagerRole) tracker.manage_addProduct['IssueTrackerProduct']\ .manage_addIssueUserFolder(keep_usernames=True) tracker.acl_users.userFolderAddUser("user", "secret", [IssueTrackerUserRole], [], email="user@test.com", fullname="User Name") # switch on issue assignment tracker.manage_UseIssueAssignmentToggle() self.assertEqual(len(tracker.getAllIssueUsers()), 1) assignee_option = tracker.getAllIssueUsers()[0] self.assertEqual(assignee_option['user'].getUserName(), 'user') # If someone else now adds an issue, a notification should # be made going out to user@test.com adn a@a.com B = u'email@address.com' Bf = u'From name' request = self.app.REQUEST request.set('title', u'TITLE') request.set('fromname', Bf) request.set('email', B) request.set('description', u'DESCRIPTION') request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) request.set('assignee', tracker.getAllIssueUsers()[0]['identifier']) request.form['notify-assignee'] = '1' tracker.SubmitIssue(request) issue = tracker.getIssueObjects()[0] # have a look at that notification object self.assertEqual(len(issue.getCreatedNotifications()), 1) notification = issue.getCreatedNotifications()[0] self.assertFalse(notification.isDispatched()) self.assertEqual(notification.getEmails(), ['user@test.com']) self.assertTrue(notification.getAssignmentObject() is not None) assignment = notification.getAssignmentObject() self.assertEqual(assignment.getEmail(), B) # who added it self.assertEqual(assignment.getAssigneeEmail(), "user@test.com") # assigned to # log in as this assignee uf = tracker.acl_users assert uf.meta_type == ISSUEUSERFOLDER_METATYPE user = uf.getUserById('user') user = user.__of__(uf) newSecurityManager(None, user) assert getSecurityManager().getUser().getUserName() == 'user' # now reply a comment as this logged in user which should # evetually nullify the notification going to this assignee request.set('comment', u'COMMENT') #request.set('fromname', Af) #request.set('email', A) request.set('notify', 1) issue.ModifyIssue(request) # there should now be a new notification object where # the latest one goes out to the submitter of the issue self.assertEqual(len(issue.getCreatedNotifications()), 1) latest_notification = issue.getCreatedNotifications()[0] self.assertEqual(latest_notification.getEmails(), [B]) def test_Real0695_bug(self): # in lack of a better name """ test that RSS and RDF feeds have the same security protection like viewing the issuetracker, the list of issues or an issue. """ tracker = self.folder.tracker request = self.app.REQUEST # Adding an issue add_issue_html = tracker.AddIssue(request) self.assertTrue(add_issue_html.find('Description:') > -1) # add an issue so there's something in the ListIssues, and the XML feeds A = u'email@address.com' Af = u'From name' request.set('title', u'TITLE') request.set('fromname', Af) request.set('email', A) request.set('description', u'DESCRIPTION') request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) tracker.SubmitIssue(request) # first of all, viewing these with the current user should be fine. #template_list_html = tracker.ListIssues(request) template_list_html = tracker.restrictedTraverse('ListIssues')(request) # expect it to say "# Issues: 0" since there are no issues added self.assertTrue(template_list_html.find('# Issues: 1') > -1) # rss.xml rss_xml = getattr(tracker, 'rss.xml')() self.assertTrue(rss_xml.find('<![CDATA[TITLE (Open)]]>') > -1) # rdf.xml rdf_xml = getattr(tracker, 'rdf.xml')() self.assertTrue(rdf_xml.find('TITLE (Open)') > -1) # Now, let's disallow anonymous access msg = tracker.manage_ViewPermissionToggle() self.assertEqual(msg, 'View permission disabled for Anonymous') # before we log out, let's create a user with the IssueTracker # IssueTrackerManagerRole self.folder.acl_users.userFolderAddUser("manager", "secret", [IssueTrackerManagerRole], []) self.folder.acl_users.userFolderAddUser("user", "secret", [IssueTrackerUserRole], []) # Now, if I log out, none of the viewings above should work self.logout() assert getSecurityManager().getUser().getUserName() == 'Anonymous User' self.assertRaises(Unauthorized, tracker.restrictedTraverse, 'ListIssues') self.assertRaises(Unauthorized, tracker.restrictedTraverse, 'rss.xml') self.assertRaises(Unauthorized, tracker.restrictedTraverse, 'rdf.xml') def test_preSubmitIssue_hook(self): """ test adding an issue with a script hook called 'pre_SubmitIssue()' """ tracker = self.folder.tracker tracker.dispatch_on_submit = False # no annoying emails on stdout adder = tracker.manage_addProduct['PythonScripts'].manage_addPythonScript adder('pre_SubmitIssue') script = getattr(tracker, 'pre_SubmitIssue') script.write(pre_submitissue_script_src) # With this hook it won't be possible to add a issue where # the subject line starts with the letter a. (silly, yes) request = self.app.REQUEST request.set('title', u'A TITLE') request.set('fromname', u'From name') request.set('email', u'email@address.com') request.set('description', u'DESCRIPTION') request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) tracker.SubmitIssue(request) self.assertEqual(len(tracker.getIssueObjects()), 0) request.set('title', u'Some TITLE') tracker.SubmitIssue(request) self.assertEqual(len(tracker.getIssueObjects()), 1) def test_postSubmitIssue_hook(self): """ test adding an issue with a script hook called 'post_SubmitIssue()' """ tracker = self.folder.tracker tracker.dispatch_on_submit = False # no annoying emails on stdout tracker.can_add_new_sections = True # add an issue user folder # Since manage_addIssueUserFolder() needs to add the two extra roles # we have to do that first because manage_addIssueUserFolder() isn't # allowed to it because it's not a POST request. tracker._addRole(IssueTrackerUserRole) tracker._addRole(IssueTrackerManagerRole) tracker.manage_addProduct['IssueTrackerProduct']\ .manage_addIssueUserFolder(keep_usernames=True) tracker.acl_users.userFolderAddUser("user", "secret", [IssueTrackerManagerRole,'Manager'], [], email="user@test.com", fullname="User Name") adder = tracker.manage_addProduct['PythonScripts'].manage_addPythonScript adder('post_SubmitIssue') script = getattr(tracker, 'post_SubmitIssue') script.write(post_submitissue_script_src) # With this hook it won't be possible to add a issue where # the subject line starts with the letter a. (silly, yes) request = self.app.REQUEST request.set('title', u'A TITLE') request.set('fromname', u'From name') request.set('email', u'email@address.com') request.set('description', u'DESCRIPTION') request.set('newsection', 'Security') request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) uf = tracker.acl_users assert uf.meta_type == ISSUEUSERFOLDER_METATYPE user = uf.getUserById('user') user = user.__of__(uf) newSecurityManager(None, user) assert getSecurityManager().getUser().getUserName() == 'user' def test_getModifyTimestamp(self): """ test issuetracker.getModifyTimestamp() """ tracker = self.folder.tracker # with no issues, the getModifyTimestamp() should be the # same as the issuetrackers' bobobase_modification_time() self.assertEqual(int(tracker.bobobase_modification_time()), tracker.getModifyTimestamp()) # if we add an issue, the issuetrackers' getModifyTimestamp() # should be that of the last added issue. request = self.app.REQUEST request.set('title', u'TITLE') request.set('fromname', u'From name') request.set('email', u'email@address.com') request.set('description', u'DESCRIPTION') request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) tracker.SubmitIssue(request) issue = tracker.getIssueObjects()[0] self.assertEqual(issue.getModifyTimestamp(), tracker.getModifyTimestamp()) def test_okFileAttachment(self): """ try to add an issue with a crap file attachment """ tracker = self.folder.tracker request = self.app.REQUEST request.set('title', u'TITLE') request.set('fromname', u'From name') request.set('email', u'email@address.com') request.set('description', u'DESCRIPTION') request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) request.set('fileattachment', NewFileUpload(os.path.abspath(__file__))) tracker.SubmitIssue(request) issue = tracker.getIssueObjects()[0] self.assertEqual(issue.countFileattachments(), 1) def test_crapFileAttachment(self): """ try to add an issue with a crap file attachment """ tracker = self.folder.tracker request = self.app.REQUEST request.set('title', u'TITLE') request.set('fromname', u'From name') request.set('email', u'email@address.com') request.set('description', u'DESCRIPTION') request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) request.set('fileattachment', DodgyNewFileUpload(os.path.abspath(__file__))) # this should fail to add an issue tracker.SubmitIssue(request) self.assertEqual(len(tracker.getIssueObjects()), 0) # this time, try to upload it with a file that is empty empty_file = os.path.join(os.path.dirname(__file__), 'size0_file.jpg') request.set('fileattachment', NewFileUpload(os.path.abspath(empty_file))) tracker.SubmitIssue(request) self.assertEqual(len(tracker.getIssueObjects()), 0) def test_saveIssueDraft(self): """ try to add an issue with a crap file attachment """ tracker = self.folder.tracker request = self.app.REQUEST title = u'TITLE'; request.set('title', title) fromname = u'From name'; request.set('fromname', fromname) email = u'email@address.com'; request.set('email', email) description = u'DESCRIPTION'; request.set('description', description) request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) request.set('fileattachment', NewFileUpload(os.path.abspath(__file__))) tracker.SaveDraftIssue(request) # Because getMyIssueDrafts() depends on cookies and cookies don't # work in ZopeTestCase :( we have to fake this a bit drafts = tracker.getDraftsContainer().objectValues(ISSUE_DRAFT_METATYPE) assert len(drafts) == 1 # Now check that draft draft = drafts[0] self.assertEqual(draft.getTitle(), title) self.assertEqual(draft.getDescription(), description) self.assertEqual(draft.getFromname(), fromname) self.assertEqual(draft.getEmail(), email) self.assertEqual(draft.getType(), tracker.getDefaultType()) self.assertEqual(draft.getUrgency(), tracker.getDefaultUrgency()) # from this it will be possible to get the files back via the # tempfolder assert request.get(TEMPFOLDER_REQUEST_KEY), "no tempfolder set in request" tempfolder = tracker._getTempFolder() files = tempfolder[request.get(TEMPFOLDER_REQUEST_KEY)].objectValues('File') assert files, "no temp files" temp_file = files[0] self.assertEqual(temp_file.getId(), os.path.basename(__file__)) def test_searchIssues(self): """ basic search tests """ tracker = self.folder.tracker request = self.app.REQUEST title = u'titles are working'; request.set('title', title) fromname = u'From name'; request.set('fromname', fromname) email = u'email@address.com'; request.set('email', email) description = u'DESCRIPTION is a in the this test' request.set('description', description) request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) request.set('fileattachment', NewFileUpload(os.path.abspath(__file__))) tracker.SubmitIssue(request) assert tracker.getIssueObjects() issue = tracker.getIssueObjects()[0] # search and find it q = 'working' self.assertEqual(issue, tracker._searchCatalog(q, search_only_on=None)[0]) self.assertEqual(issue, tracker._searchCatalog(q, search_only_on='title')[0]) # don't expect to find it q = 'notmentioned' self.assertEqual(tracker._searchCatalog(q, search_only_on='description'), []) self.assertEqual(tracker._searchCatalog(q), []) # search fuzzy and find it q = 'title' self.assertEqual(issue, tracker._searchCatalog(q)[0]) # it should be case insensitive q = 'DeScriPtion' self.assertEqual(issue, tracker._searchCatalog(q)[0]) # low level test of searching by filename self.assertEqual(issue, tracker._searchByFilename(os.path.basename(__file__))[0]) # and do it fuzzy self.assertEqual(issue, tracker._searchByFilename(os.path.basename(__file__).upper())[0]) # or without the extension name = os.path.splitext(os.path.basename(__file__))[0] self.assertEqual(issue, tracker._searchByFilename(name)[0]) # Post a followup and expect to be able to search and find it request.set('comment', u'COMMENT') this_file = os.path.abspath(__file__) a_file = os.listdir(os.path.dirname(this_file))[-1] a_file = os.path.join(os.path.dirname(this_file), a_file) request.set('fileattachment', NewFileUpload(a_file)) issue.ModifyIssue(request) thread = issue.getThreadObjects()[0] # search for the comment q = 'COMmenT' # and expect to find it's parent object which is the issue self.assertEqual(issue, tracker._searchCatalog(q)[0]) # when finding things by threads it puts this into REQUEST self.assertEqual(request.get('FirstThreadResultId'), issue.getId()) # search for a threads file q = os.path.basename(a_file) # and expect to find it's parent object which is the issue self.assertEqual(issue, tracker._searchCatalog(q)[0]) # and a variation of that q = os.path.splitext(q)[0] self.assertEqual(issue, tracker._searchCatalog(q)[0]) # If you run the UpdateEverything() we can expect the same # content in the catalog tracker.manage_delObjects(['ICatalog']) tracker.UpdateEverything() # search for the comment q = 'COMmenT' # and expect to find it's parent object which is the issue self.assertEqual(issue, tracker._searchCatalog(q)[0]) # when finding things by threads it puts this into REQUEST self.assertEqual(request.get('FirstThreadResultId'), issue.getId()) # search for a threads file q = os.path.basename(a_file) # and expect to find it's parent object which is the issue self.assertEqual(issue, tracker._searchCatalog(q)[0]) # and a variation of that q = os.path.splitext(q)[0] self.assertEqual(issue, tracker._searchCatalog(q)[0]) def test_filterIssues(self): """ test to call filter issues and note how the filter should be saved and should be reusable later. """ tracker = self.folder.tracker request = self.app.REQUEST # first, the user who performs this test is a normal zope acl # user. Let's add a name and email so that we can make sure # this is information is saved in the saved filter self.set_cookie(tracker.getCookiekey('name'), u'Bob') self.set_cookie(tracker.getCookiekey('email'), u'bob@test.com') # initially there shouldn't be a folder called 'saved-filters' self.assertFalse(hasattr(tracker, 'saved-filters')) # and there shouldn't be a zcatalog called 'saved-filters-catalog' self.assertFalse(hasattr(tracker, 'saved-filters-catalog')) # On the homepage, the links to see only say issues "On hold" is # /ListIssues?Filterlogic=show&f-statuses=on%20hold # Let's mimick that: request.set('Filterlogic','show') request.set('f-statuses','on hold') tracker.ListIssuesFiltered() # this should now have create the saved-filters folder # and the saved-filters-catalog self.assertTrue(hasattr(tracker, 'saved-filters')) self.assertTrue(hasattr(tracker, 'saved-filters-catalog')) # let's look at what was created in the saved-filters folder saved_filters = getattr(tracker, 'saved-filters').objectValues() self.assertEqual(len(saved_filters), 1) # since I'm here logged into Zope as a normal Zope user # we can expect to find that the saved filter should be to me zopeuser = tracker.getZopeUser() path = '/'.join(zopeuser.getPhysicalPath()) name = zopeuser.getUserName() acl_adder = ','.join([path, name]) saved_filter = saved_filters[0] self.assertEqual(saved_filter.acl_adder, acl_adder) self.assertEqual(saved_filter.getTitle(), u"Only on hold issues") # this is who created the filter (quite unimportant) self.assertEqual(saved_filter.adder_fromname, u'Bob') self.assertEqual(saved_filter.adder_email, 'bob@test.com') # and we didn't need to associate with a cookie key self.assertEqual(saved_filter.key, '') # the logic was to show self.assertEqual(saved_filter.filterlogic, 'show') # some attributes are automatically set for the issue metadata self.assertEqual(saved_filter.sections, None) self.assertEqual(saved_filter.urgencies, None) self.assertEqual(saved_filter.types, None) self.assertEqual(saved_filter.statuses, [u'on hold']) # We should have a saved-filters-catalog created catalog = tracker.getFilterValuerCatalog() self.assertTrue(catalog is not None) # and there should only be one brain it right now self.assertTrue(len(catalog.searchResults()) == 1) saved_filter_from_brain = catalog.searchResults()[0].getObject() self.assertTrue(saved_filter_from_brain == saved_filter) # the high level function getMySavedFilters() uses the catalog # to extract the saved filters with the most recent one # first. saved_filter_from_mysavedfilters = tracker.getMySavedFilters()[0] self.assertTrue(saved_filter_from_mysavedfilters == saved_filter) # If you run ListIssuesFiltered() again, it should have to create # one more new saved filter tracker.ListIssuesFiltered() saved_filters = getattr(tracker, 'saved-filters').objectValues() self.assertEqual(len(saved_filters), 1) # But if we change the parameters a little bit it should have # created a new saved filter request.set('f-statuses','taken') tracker.ListIssuesFiltered() saved_filters = getattr(tracker, 'saved-filters').objectValues() self.assertEqual(len(saved_filters), 2) # getMySavedFilters() is smart in that it returns the filters ordered. # Test that the more recent one comes first saved_filters_from_mysavedfilters = tracker.getMySavedFilters() self.assertEqual(saved_filters_from_mysavedfilters[0].statuses, [u'taken']) # Test the function getCurrentlyUsedSavedFilter(request_only=True) assert tracker.getCurrentlyUsedSavedFilter() is None saved_filter_id = tracker.getCurrentlyUsedSavedFilter(request_only=False) # now check that this is the correct one self.assertEqual(saved_filter_id, saved_filters_from_mysavedfilters[0].getId()) # another (more long winded) way of checking this is by that since the # last filter was to filter by "taken". Check that this is what the # filter does that comes from getCurrentlyUsedSavedFilter(request_only=False) current_saved_filter = getattr(getattr(tracker, 'saved-filters'), saved_filter_id) self.assertEqual(current_saved_filter.statuses, [u'taken']) # Some options that can be passed directly to _ListIssuesFiltered # are: # skip_filter # skip_sort # # To set these for ListIssuesFiltered() you have to put them in the # REQUEST. These are useful if you for example want to ignore possibly # filters in session such as for the homepage where it uses # ListIssuesFiltered() but without any filtering. # The skip_sort is useful to set when you don't want any sorting since # sorting will only cost time. # XXX: Only able to test this WITH issues def test_filterIssues_anonymous_user(self): """ test filtering issues when the user is not logged in """ # when *not* logged in there are two ways to remember a saved filter: # by name and email # by a cookie key # Let's first try to filter issues as a complete nobody noSecurityManager() tracker = self.folder.tracker request = self.app.REQUEST tracker.set_cookie = self.set_cookie # On the homepage, the links to see only say issues "On hold" is # /ListIssues?Filterlogic=show&f-statuses=on%20hold # Let's mimick that: request.set('Filterlogic','show') request.set('f-statuses','on hold') tracker.ListIssuesFiltered() # let's look at what was created in the saved-filters folder saved_filters = getattr(tracker, 'saved-filters').objectValues() self.assertEqual(len(saved_filters), 1) saved_filter = saved_filters[0] self.assertTrue(saved_filter.getKey() in request.cookies.values()) # run it again and it shouldn't create another saved filter tracker.ListIssuesFiltered() saved_filters = getattr(tracker, 'saved-filters').objectValues() self.assertEqual(len(saved_filters), 1) def test_filterIssues_anonymous_named_user(self): """ test filtering when the user is no logged in but has a name and email in the cookie. """ noSecurityManager() tracker = self.folder.tracker request = self.app.REQUEST tracker.set_cookie = self.set_cookie self.set_cookie(tracker.getCookiekey('name'), u'Bob') self.set_cookie(tracker.getCookiekey('email'), u'bob@test.com') # On the homepage, the links to see only say issues "On hold" is # /ListIssues?Filterlogic=show&f-statuses=on%20hold # Let's mimick that: request.set('Filterlogic','show') request.set('f-statuses','on hold') tracker.ListIssuesFiltered() # let's look at what was created in the saved-filters folder saved_filters = getattr(tracker, 'saved-filters').objectValues() self.assertEqual(len(saved_filters), 1) saved_filter = saved_filters[0] self.assertEqual(saved_filter.adder_email, 'bob@test.com') self.assertEqual(saved_filter.adder_fromname, u'Bob') # run it again and it shouldn't create another saved filter tracker.ListIssuesFiltered() saved_filters = getattr(tracker, 'saved-filters').objectValues() self.assertEqual(len(saved_filters), 1) def test_filterIssues_anonymous_named_user_no_email(self): """ test filtering when the user is no logged in but has a name and email in the cookie. """ noSecurityManager() tracker = self.folder.tracker request = self.app.REQUEST tracker.set_cookie = self.set_cookie self.set_cookie(tracker.getCookiekey('name'), u'Bob') # On the homepage, the links to see only say issues "On hold" is # /ListIssues?Filterlogic=show&f-statuses=on%20hold # Let's mimick that: request.set('Filterlogic','show') request.set('f-statuses','on hold') tracker.ListIssuesFiltered() # let's look at what was created in the saved-filters folder saved_filters = getattr(tracker, 'saved-filters').objectValues() self.assertEqual(len(saved_filters), 1) saved_filter = saved_filters[0] self.assertEqual(saved_filter.adder_fromname, u'Bob') # run it again and it shouldn't create another saved filter tracker.ListIssuesFiltered() saved_filters = getattr(tracker, 'saved-filters').objectValues() self.assertEqual(len(saved_filters), 1) def test_filterIssues_anonymous_bot(self): """ When the http user agent is a bot (Googlebot, msnbot etc.) don't save the filter persistently. """ noSecurityManager() tracker = self.folder.tracker request = self.app.REQUEST request.set('HTTP_USER_AGENT', "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)") # On the homepage, the links to see only say issues "On hold" is # /ListIssues?Filterlogic=show&f-statuses=on%20hold # Let's mimick that: request.set('Filterlogic','show') request.set('f-statuses','on hold') tracker.ListIssuesFiltered() # there shouldn't even be a folder called 'saved-filters' self.assertEqual(getattr(tracker, 'saved-filters', None), None) def test_filterIssues_recycleable(self): """ test to call filter issues and note how the filter should be saved and should be reusable later. When you *go back* to run a filter you've already done before it should be able to reuse an existing object instead of having to create a new one.""" tracker = self.folder.tracker request = self.app.REQUEST # 1 request.set('Filterlogic','show') request.set('f-statuses','on hold') tracker.ListIssuesFiltered() saved_filters = getattr(tracker, 'saved-filters').objectValues() self.assertEqual(len(saved_filters), 1) # 2 request.set('f-statuses','taken') tracker.ListIssuesFiltered() saved_filters = getattr(tracker, 'saved-filters').objectValues() self.assertEqual(len(saved_filters), 2) # 3 request.set('f-statuses','on hold') tracker.ListIssuesFiltered() saved_filters = getattr(tracker, 'saved-filters').objectValues() self.assertEqual(len(saved_filters), 2) def test_filterIssues_from_cookie_after_purge(self): """ If all the saved-filters are deleted and someone has a cookie of an old savedfilter, if the saved filter is deleted, getting it from the cookie shouldn't raise an AttributeError. """ tracker = self.folder.tracker request = self.app.REQUEST tracker.set_cookie = self.set_cookie ckey = tracker.getCookiekey('remember_savedfilter_persistently') tracker.set_cookie(ckey, 1) # 1 request.set('Filterlogic','show') request.set('f-statuses','on hold') tracker.ListIssuesFiltered() self.assertTrue('__issuetracker_savedfilter_id-tracker' in request.cookies) saved_filters = getattr(tracker, 'saved-filters').objectValues() self.assertEqual(len(saved_filters), 1) # now, mess with it tracker.manage_delObjects(['saved-filters','saved-filters-catalog']) # if the cookie causes an AttributeError, then ListIssuesFiltered() # here wouldn't work. Just running this is the final test. tracker.ListIssuesFiltered() def test_clean_saved_filters(self): """ CleanOldSavedFilters() is called by manage_UpdateEverything() but also if the number of saved filters exceeds FILTERVALUEFOLDER_THRESHOLD_CLEANING. We'll first try the manual way of cleaning. """ tracker = self.folder.tracker container = tracker._getFilterValueContainer() for i in range(100): oid = 'random-%s' % i instance = FilterValuer(oid, 'filter name') container._setObject(oid, instance) valuer = container._getOb(oid) if randint(1, 5) == 1: valuer.set('acl_adder', 'fake acl adder') elif randint(1, 4) == 1: valuer.set('key', str(randint(1, 10))) else: valuer.set('adder_email', 'email') valuer.mod_date = DateTime(time() - randint(1000, 1000000)*10) valuer.index_object() max_ = FILTERVALUEFOLDER_THRESHOLD_CLEANING count_filtervaluers = len(container.objectIds()) assert count_filtervaluers < max_ # there should also be equally many indexed objects as there # are reported by len(objectids) catalog = tracker.getFilterValuerCatalog() search = {'meta_type': FILTEROPTION_METATYPE} brains = catalog.searchResults(**search) assert len(brains) == count_filtervaluers # there are now 100 old saved filters whose # bobobase_modification_time ranges between 4 to 0 months old msg = tracker.CleanOldSavedFilters() msg_regex = re.compile('Deleted (\d+) old saved filters') no_deleted = int(msg_regex.findall(msg)[0]) self.assertTrue(no_deleted < count_filtervaluers) left = count_filtervaluers - no_deleted brains = catalog.searchResults(**search) assert len(brains) == left, \ "no. brains=%s, left=%s" %(len(brains), left) def test_clean_saved_filters_then_implode(self): """ Similar to test_clean_saved_filters() except this time we're making sure ALL saved filters are so old that if we send implode_if_possible=True to CleanOldSavedFilters() and expect the container to disappear. """ tracker = self.folder.tracker container = tracker._getFilterValueContainer() for i in range(100): oid = 'random-%s' % i instance = FilterValuer(oid, 'filter name') container._setObject(oid, instance) valuer = container._getOb(oid) if randint(1, 5) == 1: valuer.set('acl_adder', 'fake acl adder') elif randint(1, 4) == 1: valuer.set('key', str(randint(1, 10))) else: valuer.set('adder_email', 'email') valuer.mod_date = DateTime(time() - randint(1000, 10000)*3600) valuer.index_object() self.assertTrue('saved-filters' in tracker.objectIds()) msg = tracker.CleanOldSavedFilters(implode_if_possible=True) self.assertTrue('saved-filters' not in tracker.objectIds()) # and the catalog should be empty catalog = tracker.getFilterValuerCatalog() search = {'meta_type': FILTEROPTION_METATYPE} brains = catalog.searchResults(**search) self.assertTrue(len(brains) == 0) def test_unicode_in_statuses(self): """ test that it's possible to set a verb:action pair to the statuses that is unicode. """ tracker = self.folder.tracker request = self.app.REQUEST for status, verb in tracker.getStatusesMerged(aslist=True): self.assertTrue(isinstance(status, unicode)) self.assertTrue(isinstance(verb, unicode)) # All the default ones are easy, lets spice it up a bit statuses_and_verbs = [u'open, open', u'taken, take', u'on hold, put on hold', u'a pr\xe9c\xe9dente, faire pr\xe9c\xe9dente', u'rejected, reject', u'completed, complete'] request.set('statuses-and-verbs', statuses_and_verbs) tracker.manage_editIssueTrackerProperties(carefulbooleans=True, REQUEST=request) for status, verb in tracker.getStatusesMerged(aslist=True): self.assertTrue(isinstance(status, unicode)) self.assertTrue(isinstance(verb, unicode)) def test_changeIssueDetails(self): """ when you change issuedetails the changes are record as an attribute in the issue. """ tracker = self.folder.tracker tracker.dispatch_on_submit = False # no annoying emails on stdout tracker.can_add_new_sections = True request = self.app.REQUEST request.set('title', u'A TITLE') request.set('fromname', u'From name') request.set('email', u'email@address.com') request.set('description', u'DESCRIPTION') request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) tracker.SubmitIssue(request) issue = tracker.getIssueObjects()[0] changes = issue.getDetailChanges() self.assertEqual(len(changes), 0) # pretend to change sections but actually not issue.editIssueDetails(sections=issue.getSections(), type=issue.getType(), urgency=issue.getUrgency()) changes = issue.getDetailChanges() self.assertEqual(len(changes), 0) # now actually change something (sections) sections_before = issue.getSections() issue.editIssueDetails(sections=['Foo']) changes = issue.getDetailChanges() self.assertEqual(len(changes), 1) change = changes[-1] self.assertEqual(change['sections']['old'], sections_before) self.assertEqual(change['sections']['new'], ['Foo']) self.assertTrue('change_date' in change) self.assertTrue(hasattr(change['change_date'], 'strftime')) # now actually change something (type) type_before = issue.getType() issue.editIssueDetails(type='feature request') changes = issue.getDetailChanges() self.assertEqual(len(changes), 1) # merged with the previous one change = changes[-1] self.assertEqual(change['type']['old'], type_before) self.assertEqual(change['type']['new'], 'feature request') # change several things urgency_before = issue.getUrgency() confidential_before = issue.isConfidential() url2issue_before = issue.getURL2Issue() tracker.use_estimated_time = True tracker.use_actual_time = True estimated_time_hours_before = issue.getEstimatedTimeHours() actual_time_hours_before = issue.getActualTimeHours() issue.editIssueDetails(urgency='low', confidential=not confidential_before, url2issue='http://www.issuetrackerproduct.com', estimated_time_hours=1, actual_time_hours=2 ) changes = issue.getDetailChanges() self.assertEqual(len(changes), 1) # merged with the previous two change = changes[-1] self.assertEqual(change['acl_adder'], '/test_folder_1_/acl_users,test_user_1_')\ def test_changeIssueDetails_twice_merged(self): """ if you change details of an issue twice within the same minute, merge the changes into one change to prevent the interface from looking like this: Today 12:16 by Peter Size: 5 6 Today 12:16 by Peter Age: 28 29 """ tracker = self.folder.tracker tracker.dispatch_on_submit = False # no annoying emails on stdout tracker.can_add_new_sections = True request = self.app.REQUEST request.set('title', u'A TITLE') request.set('fromname', u'From name') request.set('email', u'email@address.com') request.set('description', u'DESCRIPTION') request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) tracker.SubmitIssue(request) issue = tracker.getIssueObjects()[0] # now actually change something (sections) sections_before = issue.getSections() issue.editIssueDetails(sections=['Foo']) changes = issue.getDetailChanges() self.assertEqual(len(changes), 1) change = changes[-1] self.assertEqual(change['sections']['old'], sections_before) self.assertEqual(change['sections']['new'], ['Foo']) self.assertTrue('change_date' in change) self.assertTrue(hasattr(change['change_date'], 'strftime')) # change the urgency too as a separate request issue.editIssueDetails(urgency='high') changes = issue.getDetailChanges() self.assertEqual(len(changes), 1) def test_changeIssueDetails_twice_void(self): """ if you change details of an issue twice within the same minute, merge the changes into one change to prevent the interface from looking like this: Today 12:16 by Peter Size: 5 6 Today 12:16 by Peter Age: 6 5 But if the change, within one minute, goes back to the same value as before, drop the change altogether. """ tracker = self.folder.tracker tracker.dispatch_on_submit = False # no annoying emails on stdout tracker.can_add_new_sections = True request = self.app.REQUEST request.set('title', u'A TITLE') request.set('fromname', u'From name') request.set('email', u'email@address.com') request.set('description', u'DESCRIPTION') request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) tracker.SubmitIssue(request) issue = tracker.getIssueObjects()[0] # now actually change something (sections) sections_before = issue.getSections() issue.editIssueDetails(sections=['Foo']) changes = issue.getDetailChanges() self.assertEqual(len(changes), 1) change = changes[-1] self.assertEqual(change['sections']['old'], sections_before) self.assertEqual(change['sections']['new'], ['Foo']) self.assertTrue('change_date' in change) self.assertTrue(hasattr(change['change_date'], 'strftime')) # change the urgency too as a separate request issue.editIssueDetails(sections=sections_before) changes = issue.getDetailChanges() self.assertEqual(len(changes), 0) def test_cataloging_issues(self): """ adding an issue and threads to that should be indexed in the ICatalog correctly. If you do an UpdateEverything they should still be there. """ tracker = self.folder.tracker tracker.dispatch_on_submit = False # no annoying emails on stdout tracker.can_add_new_sections = True request = self.app.REQUEST request.set('title', u'A TITLE') request.set('fromname', u'From name') request.set('email', u'email@address.com') request.set('description', u'DESCRIPTION') request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) tracker.SubmitIssue(request) issue = tracker.getIssueObjects()[0] request.set('comment', u'COMMENT') issue.ModifyIssue(request) self.assertEqual(len(tracker.getIssueObjects()), 1) self.assertEqual(len(issue.getThreadObjects()), 1) # lets check what's in the catalog catalog = tracker.getCatalog() # seaching inside it for path '/' should find two brains brains = catalog.searchResults(path='/') self.assertEqual(len(brains), 2) meta_types = [x.meta_type for x in brains] self.assertTrue('Issue Tracker Issue Thread' in meta_types) self.assertTrue('Issue Tracker Issue' in meta_types) # If we run UpdateEverything, the same content is expected in the # catalog tracker.UpdateEverything() brains = catalog.searchResults(path='/') self.assertEqual(len(brains), 2) meta_types = [x.meta_type for x in brains] self.assertTrue('Issue Tracker Issue Thread' in meta_types) self.assertTrue('Issue Tracker Issue' in meta_types) def test_assign_on_submit_email(self): """ Make sure the issue title is in the subjectline when you assign an issue to someone """ tracker = self.folder.tracker # set a valid sitemaster email address tracker.sitemaster_email = 'peter@test.com' # switch on assignment tracker.use_issue_assignment = True # add a user to assign stuff to tracker._addRole(IssueTrackerUserRole) tracker._addRole(IssueTrackerManagerRole) tracker.manage_addProduct['IssueTrackerProduct']\ .manage_addIssueUserFolder(keep_usernames=True) tracker.acl_users.userFolderAddUser("user", "secret", [IssueTrackerUserRole], [], email="user@test.com", fullname="User Name") user = tracker.getAllIssueUsers()[0] request = self.app.REQUEST request.set('title', u'A TITLE') request.set('fromname', u'From name') request.set('email', u'email@address.com') request.set('description', u'DESCRIPTION') request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) request.set('assignee', user['identifier']) request.form['notify-assignee'] = '1' no_trapped_emails = len(self.snatched_emails) tracker.SubmitIssue(request) # There should now be one issue... issues = tracker.getIssueObjects() self.assertEqual(len(issues), 1) issue = issues[0] # and it should have an assignment assignments = issue.getAssignments() self.assertEqual(len(assignments), 1) assignment = assignments[0] self.assertEqual(assignment.getFromname(), request.get('fromname')) self.assertEqual(assignment.getEmail(), request.get('email')) self.assertEqual(assignment.getAssigneeFullname(), u"User Name") self.assertEqual(assignment.getAssigneeEmail(), "user@test.com") # Look at the notifications inside the issue notifications = issue.getCreatedNotifications() self.assertEqual(len(notifications), 1) notification = notifications[0] assert len(self.snatched_emails) == no_trapped_emails + 1 # check that the assert notification.dispatched latest_email = self.snatched_emails[-1] self.assertTrue(latest_email['subject'].count(request.get('title'))) # then, later change the assignment and that should trigger another # email notifications tracker.acl_users.userFolderAddUser("user2", "secret", [IssueTrackerUserRole], [], email="user2@test.com", fullname="Second Name") users = tracker.getAllIssueUsers() user2 = [x for x in users if x['identifier'].endswith('user2')][0] uf = tracker.acl_users user = uf.getUserById('user') user = user.__of__(uf) newSecurityManager(None, user) assert getSecurityManager().getUser().getUserName() == 'user' issue.changeAssignment(user2['identifier'], send_email=True) assert len(self.snatched_emails) == no_trapped_emails + 2 assignments = issue.getAssignments() self.assertEqual(len(assignments), 2) # Look at the notifications inside the issue notifications = issue.getCreatedNotifications() self.assertEqual(len(notifications), 2) notification = notifications[-1] assert notification.dispatched latest_email = self.snatched_emails[-1] self.assertTrue(latest_email['subject'].count(request.get('title'))) self.assertTrue(latest_email['to'], 'user2@test.com') def test_showStrftimeFriendly(self): """test the base script showStrftimeFriendly()""" tracker = self.folder.tracker func = tracker.showStrftimeFriendly tests = [('%d/%m %Y', 'DD/MM YYYY'), ('%d/%m %Y %H:%M', 'DD/MM YYYY hh:mm'), ('%d %B %Y', 'DD month YYYY'), ('%d %B %Y %H:%M', 'DD month YYYY hh:mm'), ] for input_, output in tests: self.assertEqual(func(input_), output) tests = [('%d/%m %Y', 'DD/MM YYYY'), ('%d/%m %Y %H:%M', 'DD/MM YYYY'), ('%d %B %Y', 'DD month YYYY'), ('%d %B %Y %H:%M', 'DD month YYYY'), ] for input_, output in tests: self.assertEqual(func(input_, strip_hour_part=True), output) def test_getDueDateCSSSelector(self): """return some suitable CSS selectors based on the due date""" tracker = self.folder.tracker func = tracker.getDueDateCSSSelector # feed it a date in the past and it will return 'dd-past' past = DateTime('2009/01/01') self.assertEqual(func(past), 'dd-past') # as a string self.assertEqual(func('2009/01/01'), 'dd-past') today = DateTime(DateTime().strftime('%Y/%m/%d')) self.assertEqual(func(today), 'dd-today') # as a string self.assertEqual(func(DateTime().strftime('%Y/%m/%d')), 'dd-today') tomorrow = DateTime(DateTime().strftime('%Y/%m/%d')) + 1 self.assertEqual(func(tomorrow), 'dd-tomorrow') # as a string self.assertEqual(func((DateTime()+1).strftime('%Y/%m/%d')), 'dd-tomorrow') future = DateTime(DateTime().strftime('%Y/%m/%d')) + 100 self.assertEqual(func(future), 'dd-future') # as a string self.assertEqual(func((DateTime()+100).strftime('%Y/%m/%d')), 'dd-future') # junk self.assertEqual(func('xajsd'), '') self.assertEqual(func(1230), '') def test_submitting_with_due_date(self): """submit an issue with a due date""" title = u"Title" description = u"Description" tracker = self.folder.tracker request = self.app.REQUEST request.set('title', title) request.set('fromname', u'B\xc3\xa9b') request.set('email', u'email@address.com') request.set('description', description) request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) request.set('due_date', '2010/01/01') tracker.SubmitIssue(request) # it should be possible to display it issue = tracker.getIssueObjects()[0] # because due dates aren't enabled yet self.assertEqual(issue.getDueDate(), None) tracker.enable_due_date = True title = u"Somethjing" description = u"Different" request.set('title', title) request.set('fromname', u'B\xc3\xa9b') request.set('email', u'email@address.com') request.set('description', description) request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) request.set('due_date', '2010/01/01') tracker.SubmitIssue(request) #issue = [x for x in tracker.getIssueObjects() if x.title==title][0] issue = tracker.getIssueObjects()[-1] self.assertEqual(issue.getDueDate(), DateTime('2010/01/01')) # But just because you've enabled it doesn't necessarily mean # you have to set it every time title = u"Entirely" description = u"Different" request.set('title', title) request.set('fromname', u'B\xc3\xa9b') request.set('email', u'email@address.com') request.set('description', description) request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) request.set('due_date', '') tracker.SubmitIssue(request) issue = tracker.getIssueObjects()[-1] self.assertEqual(issue.getDueDate(), None) def test_editing_due_date(self): """test changing date""" tracker = self.folder.tracker tracker.enable_due_date = True title = u"Title" description = u"Description" request = self.app.REQUEST request.set('title', title) request.set('fromname', u'B\xc3\xa9b') request.set('email', u'email@address.com') request.set('description', description) request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) request.set('due_date', '2010/01/01') tracker.SubmitIssue(request) # it should be possible to display it issue = tracker.getIssueObjects()[0] #pprint(list(issue.detail_changes)) assert not issue.detail_changes self.assertEqual(issue.getDueDate(), DateTime('2010/01/01')) issue.editIssueDetails(due_date='2010/02/02') # there should now be one item in the issue.detail_changes assert len(issue.detail_changes) == 1 change = issue.detail_changes[0] # it should have a key called 'due_date' assert change['due_date'] self.assertEqual(change['due_date']['old'], DateTime('2010/01/01')) self.assertEqual(change['due_date']['new'], DateTime('2010/02/02')) self.assertEqual(issue.getDueDate(), DateTime('2010/02/02')) issue.editIssueDetails(due_date='') self.assertEqual(issue.getDueDate(), None) issue.editIssueDetails(due_date='2010/03/03') self.assertEqual(issue.getDueDate(), DateTime('2010/03/03')) def test_submitting_bad_due_dates(self): """test setting faux due dates""" tracker = self.folder.tracker title = u"Title" description = u"Description" tracker.enable_due_date = True request = self.app.REQUEST request.set('title', title) request.set('fromname', u'B\xc3\xa9b') request.set('email', u'email@address.com') request.set('description', description) request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) request.set('due_date', 'xxx') html = tracker.SubmitIssue(request) self.assertTrue(html.count('Invalid date')) # only 28 days in feb request.set('due_date', '2009/02/29') html = tracker.SubmitIssue(request) self.assertTrue(html.count('Invalid date')) assert not list(tracker.getIssueObjects()) def test_parseDueDate(self): """parseDueDate() takes a date string and returns a date object or None""" tracker = self.folder.tracker func = tracker.parseDueDate self.assertEqual(func('2009/01/01'), DateTime('2009/01/01')) self.assertEqual(func('13 april 2009'), DateTime('13 april 2009')) self.assertEqual(func('today'), DateTime(DateTime().strftime('%Y/%m/%d'))) self.assertEqual(func('tomorrow'), DateTime((DateTime()+1).strftime('%Y/%m/%d'))) self.assertEqual(func('junk'), None) self.assertEqual(func('2009/02/29'), None) # but 2008 was a leap year self.assertEqual(func('2008/02/29'), DateTime('2008/02/29')) def test_filter_by_due_date(self): """test to list issues with a filter""" tracker = self.folder.tracker title = u"Title" description = u"Description" tracker.enable_due_date = True def hourless(d): return DateTime(d.strftime('%Y/%m/%d')) today = hourless(DateTime()) yesterday = hourless(DateTime()-1) tomorrow = hourless(DateTime()+1) future = hourless(DateTime()+7) title_no_due_date = 'NO DUE DATE' title_today = 'TODAY' title_yesterday = 'YESTERDAY' title_tomorrow = 'TOMORROW' title_future = 'FUTURE' request = self.app.REQUEST request.set('fromname', u'B\xc3\xa9b') request.set('email', u'email@address.com') request.set('description', description) request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) request.set('title', title_no_due_date) request.set('due_date', '') html = tracker.SubmitIssue(request) request.set('title', title_today) request.set('due_date', today) html = tracker.SubmitIssue(request) request.set('title', title_yesterday) request.set('due_date', yesterday) html = tracker.SubmitIssue(request) request.set('title', title_tomorrow) request.set('due_date', tomorrow) html = tracker.SubmitIssue(request) request.set('title', title_future) request.set('due_date', future) html = tracker.SubmitIssue(request) seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 5) request.set('Filterlogic', 'block') request.set('filteroptions', 1) request.set('f-due', '') seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 5) request.set('f-due', 'FutUrE') # case insensitive seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 4) self.assertTrue(title_future not in [x.title for x in seq]) request.set('f-due', 'Overdue') seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 4) self.assertTrue(title_yesterday not in [x.title for x in seq]) request.set('f-due', 'Today') seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 4) self.assertTrue(title_today not in [x.title for x in seq]) request.set('f-due', 'TomorroW') seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 4) self.assertTrue(title_tomorrow not in [x.title for x in seq]) # combos request.set('f-due', ['FutUrE','Today']) seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 3) self.assertTrue(title_future not in [x.title for x in seq]) self.assertTrue(title_today not in [x.title for x in seq]) request.set('f-due', ['Overdue','Today']) seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 3) self.assertTrue(title_yesterday not in [x.title for x in seq]) self.assertTrue(title_today not in [x.title for x in seq]) request.set('f-due', ['FutUrE','OverDUE']) seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 3) self.assertTrue(title_future not in [x.title for x in seq]) self.assertTrue(title_yesterday not in [x.title for x in seq]) # Change the filter logic request.set('Filterlogic', 'show') request.set('f-due', '') seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 5) request.set('f-due', 'TODAy') seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 1) self.assertTrue(title_today in [x.title for x in seq]) request.set('f-due', 'OVERdue') seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 1) self.assertTrue(title_yesterday in [x.title for x in seq]) request.set('f-due', 'tomorrow') seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 1) self.assertTrue(title_tomorrow in [x.title for x in seq]) request.set('f-due', 'futurE') seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 1) self.assertTrue(title_future in [x.title for x in seq]) # combos request.set('f-due', ['tomorrow','future']) seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 2) self.assertTrue(title_tomorrow in [x.title for x in seq]) self.assertTrue(title_future in [x.title for x in seq]) request.set('f-due', ['overdue','future']) seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 2) self.assertTrue(title_yesterday in [x.title for x in seq]) self.assertTrue(title_future in [x.title for x in seq]) request.set('f-due', ['overdue','today']) seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 2) self.assertTrue(title_yesterday in [x.title for x in seq]) self.assertTrue(title_today in [x.title for x in seq]) def test_sort_by_due_date(self): """test to sort issues by due date""" tracker = self.folder.tracker title = u"Title" description = u"Description" tracker.enable_due_date = True def hourless(d): return DateTime(d.strftime('%Y/%m/%d')) today = hourless(DateTime()) yesterday = hourless(DateTime()-1) tomorrow = hourless(DateTime()+1) future = hourless(DateTime()+7) title_no_due_date = 'NO DUE DATE' title_today = 'TODAY' title_yesterday = 'YESTERDAY' title_tomorrow = 'TOMORROW' title_future = 'FUTURE' request = self.app.REQUEST request.set('fromname', u'B\xc3\xa9b') request.set('email', u'email@address.com') request.set('description', description) request.set('type', tracker.getDefaultType()) request.set('urgency', tracker.getDefaultUrgency()) request.set('title', title_no_due_date) request.set('due_date', '') html = tracker.SubmitIssue(request) request.set('title', title_today) request.set('due_date', today) html = tracker.SubmitIssue(request) request.set('title', title_yesterday) request.set('due_date', yesterday) html = tracker.SubmitIssue(request) request.set('title', title_tomorrow) request.set('due_date', tomorrow) html = tracker.SubmitIssue(request) request.set('title', title_future) request.set('due_date', future) html = tracker.SubmitIssue(request) request.set('sortorder', 'due_date') seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 5) # If you sort by due date, the most pressing one should come first. # The most pressing one is the one that was due longest ago. # Last should be any issues that don't have a due date. self.assertEqual([x.getDueDate() for x in seq], [yesterday, today, tomorrow, future, None]) request.set('reverse', 'true') seq = tracker.ListIssuesFiltered() self.assertEqual(len(seq), 5) self.assertEqual([x.getDueDate() for x in seq], [future, tomorrow, today, yesterday, None]) def test_change_password(self): """test to change your password when you're an acl user in a Issue User Folder""" tracker = self.folder.tracker # create an Issue User Folder inside tracker._addRole(IssueTrackerUserRole) tracker._addRole(IssueTrackerManagerRole) tracker.manage_addProduct['IssueTrackerProduct']\ .manage_addIssueUserFolder(keep_usernames=True) uf = tracker.acl_users uf.userFolderAddUser("user", "secret", [IssueTrackerUserRole], [], email="user@test.com", fullname="User Name") # first of all, you can't change your password if you're # not logged in self.assertRaises(UserSubmitError, tracker.IssueUserChangePassword, "secret", "newpassword", "newpassword") user = uf.getUserById('user') user = user.__of__(uf) newSecurityManager(None, user) assert getSecurityManager().getUser().getUserName() == 'user' # I should now be able to change my password self.assertRaises(DataSubmitError, tracker.IssueUserChangePassword, "not right", "newpassword", "newpassword") self.assertRaises(DataSubmitError, tracker.IssueUserChangePassword, "secret", "new is", "different") tracker.IssueUserChangePassword("secret", "newpass", "newpass") ################################################################################ def test_suite(): from unittest import TestSuite, makeSuite suite = TestSuite() suite.addTest(makeSuite(IssueTrackerTestCase)) if traversing: suite.addTest(makeSuite(TestFunctionalBase)) return suite if __name__ == '__main__': framework()