PageRenderTime 51ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/trac/ticket/tests/default_workflow.py

https://bitbucket.org/bluezoo/trac
Python | 402 lines | 348 code | 27 blank | 27 comment | 2 complexity | 59b155d0611cc494e86d98cedd1f1b24 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright (C) 2014 Edgewall Software
  4. # All rights reserved.
  5. #
  6. # This software is licensed as described in the file COPYING, which
  7. # you should have received as part of this distribution. The terms
  8. # are also available at http://trac.edgewall.org/wiki/TracLicense.
  9. #
  10. # This software consists of voluntary contributions made by many
  11. # individuals. For the exact contribution history, see the revision
  12. # history and logs, available at http://trac.edgewall.org/log/.
  13. from __future__ import print_function
  14. import os
  15. import tempfile
  16. import unittest
  17. from trac.perm import PermissionSystem
  18. from trac.test import EnvironmentStub, MockRequest
  19. from trac.ticket.api import TicketSystem
  20. from trac.ticket.batch import BatchModifyModule
  21. from trac.ticket.model import Component, Ticket
  22. from trac.ticket.web_ui import TicketModule
  23. from trac.util import create_file
  24. from trac.util.datefmt import to_utimestamp
  25. from trac.web.api import RequestDone
  26. from tracopt.perm.authz_policy import AuthzPolicy
  27. class ConfigurableTicketWorkflowTestCase(unittest.TestCase):
  28. def setUp(self):
  29. self.env = EnvironmentStub(default_data=True)
  30. config = self.env.config
  31. config.set('ticket-workflow', 'change_owner', 'new -> new')
  32. config.set('ticket-workflow', 'change_owner.operations', 'set_owner')
  33. self.ctlr = TicketSystem(self.env).action_controllers[0]
  34. self.ticket_module = TicketModule(self.env)
  35. def tearDown(self):
  36. self.env.reset_db()
  37. def _add_component(self, name='test', owner='owner1'):
  38. component = Component(self.env)
  39. component.name = name
  40. component.owner = owner
  41. component.insert()
  42. def test_get_all_actions_custom_attribute(self):
  43. """Custom attribute in ticket-workflow."""
  44. config = self.env.config['ticket-workflow']
  45. config.set('resolve.set_milestone', 'reject')
  46. all_actions = self.ctlr.get_all_actions()
  47. resolve_action = None
  48. for name, attrs in all_actions.items():
  49. if name == 'resolve':
  50. resolve_action = attrs
  51. self.assertIsNotNone(resolve_action)
  52. self.assertIn('set_milestone', resolve_action.keys())
  53. self.assertEqual('reject', resolve_action['set_milestone'])
  54. def test_owner_from_component(self):
  55. """Verify that the owner of a new ticket is set to the owner
  56. of the component.
  57. """
  58. self._add_component('component3', 'cowner3')
  59. req = MockRequest(self.env, method='POST', args={
  60. 'field_reporter': 'reporter1',
  61. 'field_summary': 'the summary',
  62. 'field_component': 'component3',
  63. })
  64. self.assertRaises(RequestDone, self.ticket_module.process_request, req)
  65. ticket = Ticket(self.env, 1)
  66. self.assertEqual('component3', ticket['component'])
  67. self.assertEqual('cowner3', ticket['owner'])
  68. def test_component_change(self):
  69. """New ticket owner is updated when the component is changed.
  70. """
  71. self._add_component('component3', 'cowner3')
  72. self._add_component('component4', 'cowner4')
  73. ticket = Ticket(self.env)
  74. ticket.populate({
  75. 'reporter': 'reporter1',
  76. 'summary': 'the summary',
  77. 'component': 'component3',
  78. 'owner': 'cowner3',
  79. 'status': 'new',
  80. })
  81. tkt_id = ticket.insert()
  82. req = MockRequest(self.env, method='POST', args={
  83. 'id': tkt_id,
  84. 'field_component': 'component4',
  85. 'submit': True,
  86. 'action': 'leave',
  87. 'view_time': str(to_utimestamp(ticket['changetime'])),
  88. })
  89. self.assertRaises(RequestDone, self.ticket_module.process_request, req)
  90. ticket = Ticket(self.env, tkt_id)
  91. self.assertEqual('component4', ticket['component'])
  92. self.assertEqual('cowner4', ticket['owner'])
  93. def test_component_change_and_owner_change(self):
  94. """New ticket owner is not updated if owner is explicitly
  95. changed.
  96. """
  97. self._add_component('component3', 'cowner3')
  98. self._add_component('component4', 'cowner4')
  99. ticket = Ticket(self.env)
  100. ticket.populate({
  101. 'reporter': 'reporter1',
  102. 'summary': 'the summary',
  103. 'component': 'component3',
  104. 'status': 'new',
  105. })
  106. tkt_id = ticket.insert()
  107. req = MockRequest(self.env, method='POST', args={
  108. 'id': tkt_id,
  109. 'field_component': 'component4',
  110. 'submit': True,
  111. 'action': 'change_owner',
  112. 'action_change_owner_reassign_owner': 'owner1',
  113. 'view_time': str(to_utimestamp(ticket['changetime'])),
  114. })
  115. self.assertRaises(RequestDone, self.ticket_module.process_request, req)
  116. ticket = Ticket(self.env, tkt_id)
  117. self.assertEqual('component4', ticket['component'])
  118. self.assertEqual('owner1', ticket['owner'])
  119. def test_old_owner_not_old_component_owner(self):
  120. """New ticket owner is not updated if old owner is not the owner
  121. of the old component.
  122. """
  123. self._add_component('component3', 'cowner3')
  124. self._add_component('component4', 'cowner4')
  125. ticket = Ticket(self.env)
  126. ticket.populate({
  127. 'reporter': 'reporter1',
  128. 'summary': 'the summary',
  129. 'component': 'component3',
  130. 'owner': 'owner1',
  131. 'status': 'new',
  132. })
  133. tkt_id = ticket.insert()
  134. req = MockRequest(self.env, method='POST', args={
  135. 'id': tkt_id,
  136. 'field_component': 'component4',
  137. 'submit': True,
  138. 'action': 'leave',
  139. 'view_time': str(to_utimestamp(ticket['changetime'])),
  140. })
  141. self.assertRaises(RequestDone, self.ticket_module.process_request, req)
  142. ticket = Ticket(self.env, tkt_id)
  143. self.assertEqual('component4', ticket['component'])
  144. self.assertEqual('owner1', ticket['owner'])
  145. def test_new_component_has_no_owner(self):
  146. """Ticket is not disowned when the component is changed to a
  147. component with no owner.
  148. """
  149. self._add_component('component3', 'cowner3')
  150. self._add_component('component4', '')
  151. ticket = Ticket(self.env)
  152. ticket.populate({
  153. 'reporter': 'reporter1',
  154. 'summary': 'the summary',
  155. 'component': 'component3',
  156. 'owner': 'cowner3',
  157. 'status': 'new',
  158. })
  159. tkt_id = ticket.insert()
  160. req = MockRequest(self.env, method='POST', args={
  161. 'id': tkt_id,
  162. 'field_component': 'component4',
  163. 'submit': True,
  164. 'action': 'leave',
  165. 'view_time': str(to_utimestamp(ticket['changetime'])),
  166. })
  167. self.assertRaises(RequestDone, self.ticket_module.process_request, req)
  168. ticket = Ticket(self.env, tkt_id)
  169. self.assertEqual('component4', ticket['component'])
  170. self.assertEqual('cowner3', ticket['owner'])
  171. class ResetActionTestCase(unittest.TestCase):
  172. def setUp(self):
  173. self.env = EnvironmentStub(default_data=True)
  174. self.perm_sys = PermissionSystem(self.env)
  175. self.ctlr = TicketSystem(self.env).action_controllers[0]
  176. self.req1 = MockRequest(self.env, authname='user1')
  177. self.req2 = MockRequest(self.env, authname='user2')
  178. self.ticket = Ticket(self.env)
  179. self.ticket['status'] = 'invalid'
  180. self.ticket.insert()
  181. def tearDown(self):
  182. self.env.reset_db()
  183. def _reload_workflow(self):
  184. self.ctlr.actions = self.ctlr.get_all_actions()
  185. def test_default_reset_action(self):
  186. """Default reset action."""
  187. self.perm_sys.grant_permission('user2', 'TICKET_ADMIN')
  188. self._reload_workflow()
  189. actions1 = self.ctlr.get_ticket_actions(self.req1, self.ticket)
  190. actions2 = self.ctlr.get_ticket_actions(self.req2, self.ticket)
  191. chgs2 = self.ctlr.get_ticket_changes(self.req2, self.ticket, '_reset')
  192. self.assertEqual(1, len(actions1))
  193. self.assertNotIn((0, '_reset'), actions1)
  194. self.assertEqual(2, len(actions2))
  195. self.assertIn((0, '_reset'), actions2)
  196. self.assertEqual('new', chgs2['status'])
  197. def test_custom_reset_action(self):
  198. """Custom reset action in [ticket-workflow] section."""
  199. config = self.env.config['ticket-workflow']
  200. config.set('_reset', '-> review')
  201. config.set('_reset.operations', 'reset_workflow')
  202. config.set('_reset.permissions', 'TICKET_BATCH_MODIFY')
  203. config.set('_reset.default', 2)
  204. self.perm_sys.grant_permission('user2', 'TICKET_BATCH_MODIFY')
  205. self._reload_workflow()
  206. actions1 = self.ctlr.get_ticket_actions(self.req1, self.ticket)
  207. actions2 = self.ctlr.get_ticket_actions(self.req2, self.ticket)
  208. chgs2 = self.ctlr.get_ticket_changes(self.req2, self.ticket, '_reset')
  209. self.assertEqual(1, len(actions1))
  210. self.assertNotIn((2, '_reset'), actions1)
  211. self.assertEqual(2, len(actions2))
  212. self.assertIn((2, '_reset'), actions2)
  213. self.assertEqual('review', chgs2['status'])
  214. class SetOwnerAttributeTestCase(unittest.TestCase):
  215. def setUp(self):
  216. self.env = EnvironmentStub(default_data=True)
  217. self.perm_sys = PermissionSystem(self.env)
  218. self.ctlr = TicketSystem(self.env).action_controllers[0]
  219. self.ticket = Ticket(self.env)
  220. self.ticket['status'] = 'new'
  221. self.ticket.insert()
  222. with self.env.db_transaction as db:
  223. for user in ('user1', 'user2', 'user3', 'user4'):
  224. db("INSERT INTO session VALUES (%s, %s, %s)", (user, 1, 0))
  225. permissions = [
  226. ('user1', 'TICKET_EDIT_CC'),
  227. ('user2', 'TICKET_EDIT_CC'),
  228. ('user2', 'TICKET_BATCH_MODIFY'),
  229. ('user3', 'TICKET_ADMIN'),
  230. ('user4', 'TICKET_VIEW'),
  231. ('user1', 'group1'),
  232. ('user2', 'group1'),
  233. ('user2', 'group2'),
  234. ('user3', 'group2'),
  235. ('user4', 'group3')
  236. ]
  237. for perm in permissions:
  238. self.perm_sys.grant_permission(*perm)
  239. self.req = MockRequest(self.env, authname='user1')
  240. self.expected = """\
  241. to <select name="action_reassign_reassign_owner" \
  242. id="action_reassign_reassign_owner"><option selected="True" \
  243. value="user1">user1</option><option value="user2">user2</option>\
  244. <option value="user3">user3</option></select>"""
  245. def _reload_workflow(self):
  246. self.ctlr.actions = self.ctlr.get_all_actions()
  247. def tearDown(self):
  248. self.env.reset_db()
  249. def test_users(self):
  250. self.env.config.set('ticket-workflow', 'reassign.set_owner',
  251. 'user1, user2, user3')
  252. self._reload_workflow()
  253. args = self.req, self.ticket, 'reassign'
  254. label, control, hints = self.ctlr.render_ticket_action_control(*args)
  255. self.assertEqual(self.expected, str(control))
  256. def test_groups(self):
  257. self.env.config.set('ticket-workflow', 'reassign.set_owner',
  258. 'group1, group2')
  259. self._reload_workflow()
  260. args = self.req, self.ticket, 'reassign'
  261. label, control, hints = self.ctlr.render_ticket_action_control(*args)
  262. self.assertEqual(self.expected, str(control))
  263. def test_permission(self):
  264. self.env.config.set('ticket-workflow', 'reassign.set_owner',
  265. 'TICKET_EDIT_CC, TICKET_BATCH_MODIFY')
  266. self._reload_workflow()
  267. args = self.req, self.ticket, 'reassign'
  268. label, control, hints = self.ctlr.render_ticket_action_control(*args)
  269. self.assertEqual(self.expected, str(control))
  270. class RestrictOwnerTestCase(unittest.TestCase):
  271. def setUp(self):
  272. tmpdir = os.path.realpath(tempfile.gettempdir())
  273. self.env = EnvironmentStub(enable=['trac.*', AuthzPolicy], path=tmpdir)
  274. self.env.config.set('trac', 'permission_policies',
  275. 'AuthzPolicy, DefaultPermissionPolicy')
  276. self.env.config.set('ticket', 'restrict_owner', True)
  277. self.perm_sys = PermissionSystem(self.env)
  278. self.env.insert_users([
  279. ('user1', '', ''), ('user2', '', ''),
  280. ('user3', '', ''), ('user4', '', '')
  281. ])
  282. self.perm_sys.grant_permission('user1', 'TICKET_MODIFY')
  283. self.perm_sys.grant_permission('user2', 'TICKET_VIEW')
  284. self.perm_sys.grant_permission('user3', 'TICKET_MODIFY')
  285. self.perm_sys.grant_permission('user4', 'TICKET_MODIFY')
  286. self.authz_file = os.path.join(tmpdir, 'trac-authz-policy')
  287. create_file(self.authz_file)
  288. self.env.config.set('authz_policy', 'authz_file', self.authz_file)
  289. self.ctlr = TicketSystem(self.env).action_controllers[0]
  290. self.req1 = MockRequest(self.env, authname='user1')
  291. self.ticket = Ticket(self.env)
  292. self.ticket['status'] = 'new'
  293. self.ticket.insert()
  294. def tearDown(self):
  295. self.env.reset_db()
  296. os.remove(self.authz_file)
  297. def _reload_workflow(self):
  298. self.ctlr.actions = self.ctlr.get_all_actions()
  299. def test_set_owner(self):
  300. """Restricted owners list contains users with TICKET_MODIFY.
  301. """
  302. ctrl = self.ctlr.render_ticket_action_control(self.req1, self.ticket,
  303. 'reassign')
  304. self.assertEqual('reassign', ctrl[0])
  305. self.assertIn('value="user1">user1</option>', str(ctrl[1]))
  306. self.assertNotIn('value="user2">user2</option>', str(ctrl[1]))
  307. self.assertIn('value="user3">user3</option>', str(ctrl[1]))
  308. self.assertIn('value="user4">user4</option>', str(ctrl[1]))
  309. def test_set_owner_fine_grained_permissions(self):
  310. """Fine-grained permission checks when populating the restricted
  311. owners list (#10833).
  312. """
  313. create_file(self.authz_file, """\
  314. [ticket:1]
  315. user4 = !TICKET_MODIFY
  316. """)
  317. ctrl = self.ctlr.render_ticket_action_control(self.req1, self.ticket,
  318. 'reassign')
  319. self.assertEqual('reassign', ctrl[0])
  320. self.assertIn('value="user1">user1</option>', str(ctrl[1]))
  321. self.assertNotIn('value="user2">user2</option>', str(ctrl[1]))
  322. self.assertIn('value="user3">user3</option>', str(ctrl[1]))
  323. self.assertNotIn('value="user4">user4</option>', str(ctrl[1]))
  324. def test_suite():
  325. suite = unittest.TestSuite()
  326. suite.addTest(unittest.makeSuite(ConfigurableTicketWorkflowTestCase))
  327. suite.addTest(unittest.makeSuite(ResetActionTestCase))
  328. suite.addTest(unittest.makeSuite(SetOwnerAttributeTestCase))
  329. suite.addTest(unittest.makeSuite(RestrictOwnerTestCase))
  330. return suite
  331. if __name__ == '__main__':
  332. unittest.main(defaultTest='test_suite')