1 #
2 # Copyright (c) 2003 Richard Jones, rjones@ekit-inc.com
3 # This module is free software, and you may redistribute it and/or modify
4 # under the same terms as Python, so long as this copyright message and
5 # disclaimer are retained in their original form.
6 #
7 # This module is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10 #
11 # $Id: test_cgi.py,v 1.11 2003-02-18 06:15:21 richard Exp $
13 import unittest, os, shutil, errno, sys, difflib, cgi, re
15 from roundup.cgi import client
16 from roundup import init, instance, password, hyperdb, date
18 class FileUpload:
19 def __init__(self, content, filename):
20 self.content = content
21 self.filename = filename
23 def makeForm(args):
24 form = cgi.FieldStorage()
25 for k,v in args.items():
26 if type(v) is type([]):
27 [form.list.append(cgi.MiniFieldStorage(k, x)) for x in v]
28 elif isinstance(v, FileUpload):
29 x = cgi.MiniFieldStorage(k, v.content)
30 x.filename = v.filename
31 form.list.append(x)
32 else:
33 form.list.append(cgi.MiniFieldStorage(k, v))
34 return form
36 class config:
37 TRACKER_NAME = 'testing testing'
38 TRACKER_WEB = 'http://testing.testing/'
40 class FormTestCase(unittest.TestCase):
41 def setUp(self):
42 self.dirname = '_test_cgi_form'
43 try:
44 shutil.rmtree(self.dirname)
45 except OSError, error:
46 if error.errno not in (errno.ENOENT, errno.ESRCH): raise
47 # create the instance
48 init.install(self.dirname, 'classic', 'anydbm')
49 init.initialise(self.dirname, 'sekrit')
50 # check we can load the package
51 self.instance = instance.open(self.dirname)
52 # and open the database
53 self.db = self.instance.open('admin')
54 self.db.user.create(username='Chef', address='chef@bork.bork.bork',
55 realname='Bork, Chef', roles='User')
56 self.db.user.create(username='mary', address='mary@test',
57 roles='User', realname='Contrary, Mary')
59 test = self.instance.dbinit.Class(self.db, "test",
60 string=hyperdb.String(),
61 boolean=hyperdb.Boolean(), link=hyperdb.Link('test'),
62 multilink=hyperdb.Multilink('test'), date=hyperdb.Date(),
63 interval=hyperdb.Interval())
65 # compile the labels re
66 classes = '|'.join(self.db.classes.keys())
67 self.FV_SPECIAL = re.compile(client.Client.FV_LABELS%classes,
68 re.VERBOSE)
70 def parseForm(self, form, classname='test', nodeid=None):
71 cl = client.Client(self.instance, None, {'PATH_INFO':'/'},
72 makeForm(form))
73 cl.classname = classname
74 cl.nodeid = nodeid
75 cl.db = self.db
76 return cl.parsePropsFromForm()
78 def tearDown(self):
79 self.db.close()
80 try:
81 shutil.rmtree(self.dirname)
82 except OSError, error:
83 if error.errno not in (errno.ENOENT, errno.ESRCH): raise
85 #
86 # form label extraction
87 #
88 def tl(self, s, c, i, a, p):
89 m = self.FV_SPECIAL.match(s)
90 self.assertNotEqual(m, None)
91 d = m.groupdict()
92 self.assertEqual(d['classname'], c)
93 self.assertEqual(d['id'], i)
94 for action in 'required add remove link note file'.split():
95 if a == action:
96 self.assertNotEqual(d[action], None)
97 else:
98 self.assertEqual(d[action], None)
99 self.assertEqual(d['propname'], p)
101 def testLabelMatching(self):
102 self.tl('<propname>', None, None, None, '<propname>')
103 self.tl(':required', None, None, 'required', None)
104 self.tl(':confirm:<propname>', None, None, 'confirm', '<propname>')
105 self.tl(':add:<propname>', None, None, 'add', '<propname>')
106 self.tl(':remove:<propname>', None, None, 'remove', '<propname>')
107 self.tl(':link:<propname>', None, None, 'link', '<propname>')
108 self.tl('test1:<prop>', 'test', '1', None, '<prop>')
109 self.tl('test1:required', 'test', '1', 'required', None)
110 self.tl('test1:add:<prop>', 'test', '1', 'add', '<prop>')
111 self.tl('test1:remove:<prop>', 'test', '1', 'remove', '<prop>')
112 self.tl('test1:link:<prop>', 'test', '1', 'link', '<prop>')
113 self.tl('test1:confirm:<prop>', 'test', '1', 'confirm', '<prop>')
114 self.tl('test-1:<prop>', 'test', '-1', None, '<prop>')
115 self.tl('test-1:required', 'test', '-1', 'required', None)
116 self.tl('test-1:add:<prop>', 'test', '-1', 'add', '<prop>')
117 self.tl('test-1:remove:<prop>', 'test', '-1', 'remove', '<prop>')
118 self.tl('test-1:link:<prop>', 'test', '-1', 'link', '<prop>')
119 self.tl('test-1:confirm:<prop>', 'test', '-1', 'confirm', '<prop>')
120 self.tl(':note', None, None, 'note', None)
121 self.tl(':file', None, None, 'file', None)
123 #
124 # Empty form
125 #
126 def testNothing(self):
127 self.assertEqual(self.parseForm({}), ({('test', None): {}}, []))
129 def testNothingWithRequired(self):
130 self.assertRaises(ValueError, self.parseForm, {':required': 'string'})
131 self.assertRaises(ValueError, self.parseForm,
132 {':required': 'title,status', 'status':'1'}, 'issue')
133 self.assertRaises(ValueError, self.parseForm,
134 {':required': ['title','status'], 'status':'1'}, 'issue')
135 self.assertRaises(ValueError, self.parseForm,
136 {':required': 'status', 'status':''}, 'issue')
137 self.assertRaises(ValueError, self.parseForm,
138 {':required': 'nosy', 'nosy':''}, 'issue')
140 #
141 # Nonexistant edit
142 #
143 def testEditNonexistant(self):
144 self.assertRaises(IndexError, self.parseForm, {'boolean': ''},
145 'test', '1')
147 #
148 # String
149 #
150 def testEmptyString(self):
151 self.assertEqual(self.parseForm({'string': ''}),
152 ({('test', None): {}}, []))
153 self.assertEqual(self.parseForm({'string': ' '}),
154 ({('test', None): {}}, []))
155 self.assertRaises(ValueError, self.parseForm, {'string': ['', '']})
157 def testSetString(self):
158 self.assertEqual(self.parseForm({'string': 'foo'}),
159 ({('test', None): {'string': 'foo'}}, []))
160 self.assertEqual(self.parseForm({'string': 'a\r\nb\r\n'}),
161 ({('test', None): {'string': 'a\nb'}}, []))
162 nodeid = self.db.issue.create(title='foo')
163 self.assertEqual(self.parseForm({'title': 'foo'}, 'issue', nodeid),
164 ({('issue', nodeid): {}}, []))
166 def testEmptyStringSet(self):
167 nodeid = self.db.issue.create(title='foo')
168 self.assertEqual(self.parseForm({'title': ''}, 'issue', nodeid),
169 ({('issue', nodeid): {'title': None}}, []))
170 nodeid = self.db.issue.create(title='foo')
171 self.assertEqual(self.parseForm({'title': ' '}, 'issue', nodeid),
172 ({('issue', nodeid): {'title': None}}, []))
174 def testFileUpload(self):
175 file = FileUpload('foo', 'foo.txt')
176 self.assertEqual(self.parseForm({'content': file}, 'file'),
177 ({('file', None): {'content': 'foo', 'name': 'foo.txt',
178 'type': 'text/plain'}}, []))
180 #
181 # Link
182 #
183 def testEmptyLink(self):
184 self.assertEqual(self.parseForm({'link': ''}),
185 ({('test', None): {}}, []))
186 self.assertEqual(self.parseForm({'link': ' '}),
187 ({('test', None): {}}, []))
188 self.assertRaises(ValueError, self.parseForm, {'link': ['', '']})
189 self.assertEqual(self.parseForm({'link': '-1'}),
190 ({('test', None): {}}, []))
192 def testSetLink(self):
193 self.assertEqual(self.parseForm({'status': 'unread'}, 'issue'),
194 ({('issue', None): {'status': '1'}}, []))
195 self.assertEqual(self.parseForm({'status': '1'}, 'issue'),
196 ({('issue', None): {'status': '1'}}, []))
197 nodeid = self.db.issue.create(status='unread')
198 self.assertEqual(self.parseForm({'status': 'unread'}, 'issue', nodeid),
199 ({('issue', nodeid): {}}, []))
201 def testUnsetLink(self):
202 nodeid = self.db.issue.create(status='unread')
203 self.assertEqual(self.parseForm({'status': '-1'}, 'issue', nodeid),
204 ({('issue', nodeid): {'status': None}}, []))
206 def testInvalidLinkValue(self):
207 # XXX This is not the current behaviour - should we enforce this?
208 # self.assertRaises(IndexError, self.parseForm,
209 # {'status': '4'}))
210 self.assertRaises(ValueError, self.parseForm, {'link': 'frozzle'})
211 self.assertRaises(ValueError, self.parseForm, {'status': 'frozzle'},
212 'issue')
214 #
215 # Multilink
216 #
217 def testEmptyMultilink(self):
218 self.assertEqual(self.parseForm({'nosy': ''}),
219 ({('test', None): {}}, []))
220 self.assertEqual(self.parseForm({'nosy': ' '}),
221 ({('test', None): {}}, []))
223 def testSetMultilink(self):
224 self.assertEqual(self.parseForm({'nosy': '1'}, 'issue'),
225 ({('issue', None): {'nosy': ['1']}}, []))
226 self.assertEqual(self.parseForm({'nosy': 'admin'}, 'issue'),
227 ({('issue', None): {'nosy': ['1']}}, []))
228 self.assertEqual(self.parseForm({'nosy': ['1','2']}, 'issue'),
229 ({('issue', None): {'nosy': ['1','2']}}, []))
230 self.assertEqual(self.parseForm({'nosy': '1,2'}, 'issue'),
231 ({('issue', None): {'nosy': ['1','2']}}, []))
232 self.assertEqual(self.parseForm({'nosy': 'admin,2'}, 'issue'),
233 ({('issue', None): {'nosy': ['1','2']}}, []))
235 def testEmptyMultilinkSet(self):
236 nodeid = self.db.issue.create(nosy=['1','2'])
237 self.assertEqual(self.parseForm({'nosy': ''}, 'issue', nodeid),
238 ({('issue', nodeid): {'nosy': []}}, []))
239 nodeid = self.db.issue.create(nosy=['1','2'])
240 self.assertEqual(self.parseForm({'nosy': ' '}, 'issue', nodeid),
241 ({('issue', nodeid): {'nosy': []}}, []))
242 self.assertEqual(self.parseForm({'nosy': '1,2'}, 'issue', nodeid),
243 ({('issue', nodeid): {}}, []))
245 def testInvalidMultilinkValue(self):
246 # XXX This is not the current behaviour - should we enforce this?
247 # self.assertRaises(IndexError, self.parseForm,
248 # {'nosy': '4'}))
249 self.assertRaises(ValueError, self.parseForm, {'nosy': 'frozzle'},
250 'issue')
251 self.assertRaises(ValueError, self.parseForm, {'nosy': '1,frozzle'},
252 'issue')
253 self.assertRaises(ValueError, self.parseForm, {'multilink': 'frozzle'})
255 def testMultilinkAdd(self):
256 nodeid = self.db.issue.create(nosy=['1'])
257 # do nothing
258 self.assertEqual(self.parseForm({':add:nosy': ''}, 'issue', nodeid),
259 ({('issue', nodeid): {}}, []))
261 # do something ;)
262 self.assertEqual(self.parseForm({':add:nosy': '2'}, 'issue', nodeid),
263 ({('issue', nodeid): {'nosy': ['1','2']}}, []))
264 self.assertEqual(self.parseForm({':add:nosy': '2,mary'}, 'issue',
265 nodeid), ({('issue', nodeid): {'nosy': ['1','2','4']}}, []))
266 self.assertEqual(self.parseForm({':add:nosy': ['2','3']}, 'issue',
267 nodeid), ({('issue', nodeid): {'nosy': ['1','2','3']}}, []))
269 def testMultilinkAddNew(self):
270 self.assertEqual(self.parseForm({':add:nosy': ['2','3']}, 'issue'),
271 ({('issue', None): {'nosy': ['2','3']}}, []))
273 def testMultilinkRemove(self):
274 nodeid = self.db.issue.create(nosy=['1','2'])
275 # do nothing
276 self.assertEqual(self.parseForm({':remove:nosy': ''}, 'issue', nodeid),
277 ({('issue', nodeid): {}}, []))
279 # do something ;)
280 self.assertEqual(self.parseForm({':remove:nosy': '1'}, 'issue',
281 nodeid), ({('issue', nodeid): {'nosy': ['2']}}, []))
282 self.assertEqual(self.parseForm({':remove:nosy': 'admin,2'},
283 'issue', nodeid), ({('issue', nodeid): {'nosy': []}}, []))
284 self.assertEqual(self.parseForm({':remove:nosy': ['1','2']},
285 'issue', nodeid), ({('issue', nodeid): {'nosy': []}}, []))
287 # add and remove
288 self.assertEqual(self.parseForm({':add:nosy': ['3'],
289 ':remove:nosy': ['1','2']},
290 'issue', nodeid), ({('issue', nodeid): {'nosy': ['3']}}, []))
292 # remove one that doesn't exist?
293 self.assertRaises(ValueError, self.parseForm, {':remove:nosy': '4'},
294 'issue', nodeid)
296 def testMultilinkRetired(self):
297 self.db.user.retire('2')
298 self.assertEqual(self.parseForm({'nosy': ['2','3']}, 'issue'),
299 ({('issue', None): {'nosy': ['2','3']}}, []))
300 nodeid = self.db.issue.create(nosy=['1','2'])
301 self.assertEqual(self.parseForm({':remove:nosy': '2'}, 'issue',
302 nodeid), ({('issue', nodeid): {'nosy': ['1']}}, []))
303 self.assertEqual(self.parseForm({':add:nosy': '3'}, 'issue', nodeid),
304 ({('issue', nodeid): {'nosy': ['1','2','3']}}, []))
306 def testAddRemoveNonexistant(self):
307 self.assertRaises(ValueError, self.parseForm, {':remove:foo': '2'},
308 'issue')
309 self.assertRaises(ValueError, self.parseForm, {':add:foo': '2'},
310 'issue')
312 #
313 # Password
314 #
315 def testEmptyPassword(self):
316 self.assertEqual(self.parseForm({'password': ''}, 'user'),
317 ({('user', None): {}}, []))
318 self.assertEqual(self.parseForm({'password': ''}, 'user'),
319 ({('user', None): {}}, []))
320 self.assertRaises(ValueError, self.parseForm, {'password': ['', '']},
321 'user')
322 self.assertRaises(ValueError, self.parseForm, {'password': 'foo',
323 ':confirm:password': ['', '']}, 'user')
325 def testSetPassword(self):
326 self.assertEqual(self.parseForm({'password': 'foo',
327 ':confirm:password': 'foo'}, 'user'),
328 ({('user', None): {'password': 'foo'}}, []))
330 def testSetPasswordConfirmBad(self):
331 self.assertRaises(ValueError, self.parseForm, {'password': 'foo'},
332 'user')
333 self.assertRaises(ValueError, self.parseForm, {'password': 'foo',
334 ':confirm:password': 'bar'}, 'user')
336 def testEmptyPasswordNotSet(self):
337 nodeid = self.db.user.create(username='1',
338 password=password.Password('foo'))
339 self.assertEqual(self.parseForm({'password': ''}, 'user', nodeid),
340 ({('user', nodeid): {}}, []))
341 nodeid = self.db.user.create(username='2',
342 password=password.Password('foo'))
343 self.assertEqual(self.parseForm({'password': '',
344 ':confirm:password': ''}, 'user', nodeid),
345 ({('user', nodeid): {}}, []))
347 #
348 # Boolean
349 #
350 def testEmptyBoolean(self):
351 self.assertEqual(self.parseForm({'boolean': ''}),
352 ({('test', None): {}}, []))
353 self.assertEqual(self.parseForm({'boolean': ' '}),
354 ({('test', None): {}}, []))
355 self.assertRaises(ValueError, self.parseForm, {'boolean': ['', '']})
357 def testSetBoolean(self):
358 self.assertEqual(self.parseForm({'boolean': 'yes'}),
359 ({('test', None): {'boolean': 1}}, []))
360 self.assertEqual(self.parseForm({'boolean': 'a\r\nb\r\n'}),
361 ({('test', None): {'boolean': 0}}, []))
362 nodeid = self.db.test.create(boolean=1)
363 self.assertEqual(self.parseForm({'boolean': 'yes'}, 'test', nodeid),
364 ({('test', nodeid): {}}, []))
365 nodeid = self.db.test.create(boolean=0)
366 self.assertEqual(self.parseForm({'boolean': 'no'}, 'test', nodeid),
367 ({('test', nodeid): {}}, []))
369 def testEmptyBooleanSet(self):
370 nodeid = self.db.test.create(boolean=0)
371 self.assertEqual(self.parseForm({'boolean': ''}, 'test', nodeid),
372 ({('test', nodeid): {'boolean': None}}, []))
373 nodeid = self.db.test.create(boolean=1)
374 self.assertEqual(self.parseForm({'boolean': ' '}, 'test', nodeid),
375 ({('test', nodeid): {'boolean': None}}, []))
377 #
378 # Date
379 #
380 def testEmptyDate(self):
381 self.assertEqual(self.parseForm({'date': ''}),
382 ({('test', None): {}}, []))
383 self.assertEqual(self.parseForm({'date': ' '}),
384 ({('test', None): {}}, []))
385 self.assertRaises(ValueError, self.parseForm, {'date': ['', '']})
387 def testSetDate(self):
388 self.assertEqual(self.parseForm({'date': '2003-01-01'}),
389 ({('test', None): {'date': date.Date('2003-01-01')}}, []))
390 nodeid = self.db.test.create(date=date.Date('2003-01-01'))
391 self.assertEqual(self.parseForm({'date': '2003-01-01'}, 'test',
392 nodeid), ({('test', nodeid): {}}, []))
394 def testEmptyDateSet(self):
395 nodeid = self.db.test.create(date=date.Date('.'))
396 self.assertEqual(self.parseForm({'date': ''}, 'test', nodeid),
397 ({('test', nodeid): {'date': None}}, []))
398 nodeid = self.db.test.create(date=date.Date('1970-01-01.00:00:00'))
399 self.assertEqual(self.parseForm({'date': ' '}, 'test', nodeid),
400 ({('test', nodeid): {'date': None}}, []))
402 #
403 # Test multiple items in form
404 #
405 def testMultiple(self):
406 self.assertEqual(self.parseForm({'string': 'a', 'issue-1@title': 'b'}),
407 ({('test', None): {'string': 'a'},
408 ('issue', '-1'): {'title': 'b'}
409 }, []))
411 def testMultipleExistingContext(self):
412 nodeid = self.db.test.create()
413 self.assertEqual(self.parseForm({'string': 'a', 'issue-1@title': 'b'},
414 'test', nodeid),({('test', nodeid): {'string': 'a'},
415 ('issue', '-1'): {'title': 'b'}}, []))
417 def testLinking(self):
418 self.assertEqual(self.parseForm({
419 'string': 'a',
420 'issue-1@add@nosy': '1',
421 'issue-2@link@superseder': 'issue-1',
422 }),
423 ({('test', None): {'string': 'a'},
424 ('issue', '-1'): {'nosy': ['1']},
425 ('issue', '-2'): {}
426 },
427 [('issue', '-2', 'superseder', [('issue', '-1')])
428 ]
429 )
430 )
432 def testLinkBadDesignator(self):
433 self.assertRaises(ValueError, self.parseForm,
434 {'test-1@link@link': 'blah'})
435 self.assertRaises(ValueError, self.parseForm,
436 {'test-1@link@link': 'issue'})
438 def testLinkNotLink(self):
439 self.assertRaises(ValueError, self.parseForm,
440 {'test-1@link@boolean': 'issue-1'})
441 self.assertRaises(ValueError, self.parseForm,
442 {'test-1@link@string': 'issue-1'})
444 def testBackwardsCompat(self):
445 res = self.parseForm({':note': 'spam'}, 'issue')
446 date = res[0][('msg', '-1')]['date']
447 self.assertEqual(res, ({('issue', None): {}, ('msg', '-1'):
448 {'content': 'spam', 'author': '1', 'date': date}},
449 [('issue', None, 'messages', [('msg', '-1')])]))
450 file = FileUpload('foo', 'foo.txt')
451 self.assertEqual(self.parseForm({':file': file}, 'issue'),
452 ({('issue', None): {}, ('file', '-1'): {'content': 'foo',
453 'name': 'foo.txt', 'type': 'text/plain'}},
454 [('issue', None, 'files', [('file', '-1')])]))
456 def suite():
457 l = [unittest.makeSuite(FormTestCase),
458 ]
459 return unittest.TestSuite(l)
461 def run():
462 runner = unittest.TextTestRunner()
463 unittest.main(testRunner=runner)
465 if __name__ == '__main__':
466 run()
468 # vim: set filetype=python ts=4 sw=4 et si