Code

Fix linking of an existing item to a newly created item, e.g. edit
[roundup.git] / test / test_actions.py
1 from __future__ import nested_scopes
3 import unittest
4 from cgi import FieldStorage, MiniFieldStorage
6 from roundup import hyperdb
7 from roundup.date import Date, Interval
8 from roundup.cgi.actions import *
9 from roundup.cgi.exceptions import Redirect, Unauthorised, SeriousError
11 from mocknull import MockNull
13 def true(*args, **kwargs):
14     return 1
16 class ActionTestCase(unittest.TestCase):
17     def setUp(self):
18         self.form = FieldStorage()
19         self.client = MockNull()
20         self.client.form = self.form
21         class TemplatingUtils:
22             pass
23         self.client.instance.interfaces.TemplatingUtils = TemplatingUtils
25 class ShowActionTestCase(ActionTestCase):
26     def assertRaisesMessage(self, exception, callable, message, *args,
27                             **kwargs):
28         """An extension of assertRaises, which also checks the exception
29         message. We need this because we rely on exception messages when
30         redirecting.
31         """
32         try:
33             callable(*args, **kwargs)
34         except exception, msg:
35             self.assertEqual(str(msg), message)
36         else:
37             if hasattr(exception, '__name__'):
38                 excName = exception.__name__
39             else:
40                 excName = str(exception)
41             raise self.failureException, excName
43     def testShowAction(self):
44         self.client.base = 'BASE/'
46         action = ShowAction(self.client)
47         self.assertRaises(ValueError, action.handle)
49         self.form.value.append(MiniFieldStorage('@type', 'issue'))
50         self.assertRaises(SeriousError, action.handle)
52         self.form.value.append(MiniFieldStorage('@number', '1'))
53         self.assertRaisesMessage(Redirect, action.handle, 'BASE/issue1')
55     def testShowActionNoType(self):
56         action = ShowAction(self.client)
57         self.assertRaises(ValueError, action.handle)
58         self.form.value.append(MiniFieldStorage('@number', '1'))
59         self.assertRaisesMessage(ValueError, action.handle,
60             'No type specified')
62 class RetireActionTestCase(ActionTestCase):
63     def testRetireAction(self):
64         self.client.db.security.hasPermission = true
65         self.client.ok_message = []
66         RetireAction(self.client).handle()
67         self.assert_(len(self.client.ok_message) == 1)
69     def testNoPermission(self):
70         self.assertRaises(Unauthorised, RetireAction(self.client).execute)
72     def testDontRetireAdminOrAnonymous(self):
73         self.client.db.security.hasPermission=true
74         # look up the user class
75         self.client.classname = 'user'
76         # but always look up admin, regardless of nodeid
77         self.client.db.user.get = lambda a,b: 'admin'
78         self.assertRaises(ValueError, RetireAction(self.client).handle)
79         # .. or anonymous
80         self.client.db.user.get = lambda a,b: 'anonymous'
81         self.assertRaises(ValueError, RetireAction(self.client).handle)
83 class SearchActionTestCase(ActionTestCase):
84     def setUp(self):
85         ActionTestCase.setUp(self)
86         self.action = SearchAction(self.client)
88 class StandardSearchActionTestCase(SearchActionTestCase):
89     def testNoPermission(self):
90         self.assertRaises(Unauthorised, self.action.execute)
92     def testQueryName(self):
93         self.assertEqual(self.action.getQueryName(), '')
95         self.form.value.append(MiniFieldStorage('@queryname', 'foo'))
96         self.assertEqual(self.action.getQueryName(), 'foo')
98 class FakeFilterVarsTestCase(SearchActionTestCase):
99     def setUp(self):
100         SearchActionTestCase.setUp(self)
101         self.client.db.classes.get_transitive_prop = lambda x: \
102             hyperdb.Multilink('foo')
104     def assertFilterEquals(self, expected):
105         self.action.fakeFilterVars()
106         self.assertEqual(self.form.getvalue('@filter'), expected)
108     def testEmptyMultilink(self):
109         self.form.value.append(MiniFieldStorage('foo', ''))
110         self.form.value.append(MiniFieldStorage('foo', ''))
112         self.assertFilterEquals(None)
114     def testNonEmptyMultilink(self):
115         self.form.value.append(MiniFieldStorage('foo', ''))
116         self.form.value.append(MiniFieldStorage('foo', '1'))
118         self.assertFilterEquals('foo')
120     def testEmptyKey(self):
121         self.form.value.append(MiniFieldStorage('foo', ''))
122         self.assertFilterEquals(None)
124     def testStandardKey(self):
125         self.form.value.append(MiniFieldStorage('foo', '1'))
126         self.assertFilterEquals('foo')
128     def testStringKey(self):
129         self.client.db.classes.getprops = lambda: {'foo': hyperdb.String()}
130         self.form.value.append(MiniFieldStorage('foo', 'hello'))
131         self.assertFilterEquals('foo')
133     def testTokenizedStringKey(self):
134         self.client.db.classes.get_transitive_prop = lambda x: hyperdb.String()
135         self.form.value.append(MiniFieldStorage('foo', 'hello world'))
137         self.assertFilterEquals('foo')
139         # The single value gets replaced with the tokenized list.
140         self.assertEqual([x.value for x in self.form['foo']],
141             ['hello', 'world'])
143 class CollisionDetectionTestCase(ActionTestCase):
144     def setUp(self):
145         ActionTestCase.setUp(self)
146         self.action = EditItemAction(self.client)
147         self.now = Date('.')
148         # round off for testing
149         self.now.second = int(self.now.second)
151     def testLastUserActivity(self):
152         self.assertEqual(self.action.lastUserActivity(), None)
154         self.client.form.value.append(
155             MiniFieldStorage('@lastactivity', str(self.now)))
156         self.assertEqual(self.action.lastUserActivity(), self.now)
158     def testLastNodeActivity(self):
159         self.action.classname = 'issue'
160         self.action.nodeid = '1'
162         def get(nodeid, propname):
163             self.assertEqual(nodeid, '1')
164             self.assertEqual(propname, 'activity')
165             return self.now
166         self.client.db.issue.get = get
168         self.assertEqual(self.action.lastNodeActivity(), self.now)
170     def testCollision(self):
171         # fake up an actual change
172         self.action.classname = 'test'
173         self.action.nodeid = '1'
174         self.client.parsePropsFromForm = lambda: ({('test','1'):{1:1}}, [])
175         self.failUnless(self.action.detectCollision(self.now,
176             self.now + Interval("1d")))
177         self.failIf(self.action.detectCollision(self.now,
178             self.now - Interval("1d")))
179         self.failIf(self.action.detectCollision(None, self.now))
181 class LoginTestCase(ActionTestCase):
182     def setUp(self):
183         ActionTestCase.setUp(self)
184         self.client.error_message = []
186         # set the db password to 'right'
187         self.client.db.user.get = lambda a,b: 'right'
189         # unless explicitly overridden, we should never get here
190         self.client.opendb = lambda a: self.fail(
191             "Logged in, but we shouldn't be.")
193     def assertLoginLeavesMessages(self, messages, username=None, password=None):
194         if username is not None:
195             self.form.value.append(MiniFieldStorage('__login_name', username))
196         if password is not None:
197             self.form.value.append(
198                 MiniFieldStorage('__login_password', password))
200         LoginAction(self.client).handle()
201         self.assertEqual(self.client.error_message, messages)
203     def testNoUsername(self):
204         self.assertLoginLeavesMessages(['Username required'])
206     def testInvalidUsername(self):
207         def raiseKeyError(a):
208             raise KeyError
209         self.client.db.user.lookup = raiseKeyError
210         self.assertLoginLeavesMessages(['Invalid login'], 'foo')
212     def testInvalidPassword(self):
213         self.assertLoginLeavesMessages(['Invalid login'], 'foo', 'wrong')
215     def testNoWebAccess(self):
216         self.assertLoginLeavesMessages(['You do not have permission to login'],
217                                         'foo', 'right')
219     def testCorrectLogin(self):
220         self.client.db.security.hasPermission = lambda *args, **kwargs: True
222         def opendb(username):
223             self.assertEqual(username, 'foo')
224         self.client.opendb = opendb
226         self.assertLoginLeavesMessages([], 'foo', 'right')
228 class EditItemActionTestCase(ActionTestCase):
229     def setUp(self):
230         ActionTestCase.setUp(self)
231         self.result = []
232         class AppendResult:
233             def __init__(inner_self, name):
234                 inner_self.name = name
235             def __call__(inner_self, *args, **kw):
236                 self.result.append((inner_self.name, args, kw))
237                 if inner_self.name == 'set':
238                     return kw
239                 return '17'
241         self.client.db.security.hasPermission = true
242         self.client.classname = 'issue'
243         self.client.base = 'http://tracker/'
244         self.client.nodeid = '4711'
245         self.client.template = 'item'
246         self.client.db.classes.create = AppendResult('create')
247         self.client.db.classes.set = AppendResult('set')
248         self.client.db.classes.getprops = lambda: \
249             ({'messages':hyperdb.Multilink('msg')
250              ,'content':hyperdb.String()
251              ,'files':hyperdb.Multilink('file')
252              ,'msg':hyperdb.Link('msg')
253              })
254         self.action = EditItemAction(self.client)
256     def testMessageAttach(self):
257         expect = \
258             [ ('create',(),{'content':'t'})
259             , ('set',('4711',), {'messages':['23','42','17']})
260             ]
261         self.client.db.classes.get = lambda a, b:['23','42']
262         self.client.parsePropsFromForm = lambda: \
263             ( {('msg','-1'):{'content':'t'},('issue','4711'):{}}
264             , [('issue','4711','messages',[('msg','-1')])]
265             )
266         try :
267             self.action.handle()
268         except Redirect, msg:
269             pass
270         self.assertEqual(expect, self.result)
272     def testFileAttach(self):
273         expect = \
274             [('create',(),{'content':'t','type':'text/plain','name':'t.txt'})
275             ,('set',('4711',),{'files':['23','42','17']})
276             ]
277         self.client.db.classes.get = lambda a, b:['23','42']
278         self.client.parsePropsFromForm = lambda: \
279             ( {('file','-1'):{'content':'t','type':'text/plain','name':'t.txt'}
280               ,('issue','4711'):{}
281               }
282             , [('issue','4711','messages',[('msg','-1')])
283               ,('issue','4711','files',[('file','-1')])
284               ,('msg','-1','files',[('file','-1')])
285               ]
286             )
287         try :
288             self.action.handle()
289         except Redirect, msg:
290             pass
291         self.assertEqual(expect, self.result)
293     def testLinkExisting(self):
294         expect = [('set',('4711',),{'messages':['23','42','1']})]
295         self.client.db.classes.get = lambda a, b:['23','42']
296         self.client.parsePropsFromForm = lambda: \
297             ( {('issue','4711'):{},('msg','1'):{}}
298             , [('issue','4711','messages',[('msg','1')])]
299             )
300         try :
301             self.action.handle()
302         except Redirect, msg:
303             pass
304         self.assertEqual(expect, self.result)
306     def testLinkNewToExisting(self):
307         expect = [('create',(),{'msg':'1','title':'TEST'})]
308         self.client.db.classes.get = lambda a, b:['23','42']
309         self.client.parsePropsFromForm = lambda: \
310             ( {('issue','-1'):{'title':'TEST'},('msg','1'):{}}
311             , [('issue','-1','msg',[('msg','1')])]
312             )
313         try :
314             self.action.handle()
315         except Redirect, msg:
316             pass
317         self.assertEqual(expect, self.result)
319 def test_suite():
320     suite = unittest.TestSuite()
321     suite.addTest(unittest.makeSuite(RetireActionTestCase))
322     suite.addTest(unittest.makeSuite(StandardSearchActionTestCase))
323     suite.addTest(unittest.makeSuite(FakeFilterVarsTestCase))
324     suite.addTest(unittest.makeSuite(ShowActionTestCase))
325     suite.addTest(unittest.makeSuite(CollisionDetectionTestCase))
326     suite.addTest(unittest.makeSuite(LoginTestCase))
327     suite.addTest(unittest.makeSuite(EditItemActionTestCase))
328     return suite
330 if __name__ == '__main__':
331     runner = unittest.TextTestRunner()
332     unittest.main(testRunner=runner)
334 # vim: set et sts=4 sw=4 :