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 :