# -*- coding: iso-8859-1 -*
##
## <peter@fry-it.com>
##
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('<title><![CDATA[TITLE #001 (Open)]]></title>') > -1)
# rdf.xml
rdf_xml = getattr(tracker, 'rdf.xml')()
self.assertTrue(rdf_xml.find('<title>TITLE (Open)</title>') > -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('<span class="submiterror">Invalid date</span>'))
# only 28 days in feb
request.set('due_date', '2009/02/29')
html = tracker.SubmitIssue(request)
self.assertTrue(html.count('<span class="submiterror">Invalid date</span>'))
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_catalog_search_by_issuedate(self):
"""it should be possible to search the issuetracker catalog by the issuedate
"""
# create an issue
request = self.app.REQUEST
tracker = self.folder.tracker
catalog = tracker.getCatalog()
self.assertTrue('issuedate' in catalog._catalog.indexes.keys())
request.set('fromname', u'B\xc3\xa9b')
request.set('email', u'email@address.com')
request.set('description', u"\xef Description")
request.set('type', tracker.getDefaultType())
request.set('urgency', tracker.getDefaultUrgency())
request.set('title', u"Sample issue")
html = tracker.SubmitIssue(request)
# find it again
# just check basic catalog search first
brains = catalog()
self.assertTrue(brains)
self.assertEqual(brains[0].getObject().getTitle(), u"Sample issue")
brains = catalog(issuedate={'query':DateTime()-1, 'range':'min'})
self.assertTrue(brains)
self.assertEqual(brains[0].getObject().getTitle(), u"Sample issue")
################################################################################
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()
|