Code

Move out parts of client.py to new modules:
[roundup.git] / test / test_cgi.py
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.22 2004-02-11 21:34:31 jlgijsbers Exp $
13 import unittest, os, shutil, errno, sys, difflib, cgi, re
15 from roundup.cgi import client
16 from roundup.cgi.errors import FormError
17 from roundup.cgi.FormParser import FormParser
18 from roundup import init, instance, password, hyperdb, date
20 NEEDS_INSTANCE = 1
22 class FileUpload:
23     def __init__(self, content, filename):
24         self.content = content
25         self.filename = filename
27 def makeForm(args):
28     form = cgi.FieldStorage()
29     for k,v in args.items():
30         if type(v) is type([]):
31             [form.list.append(cgi.MiniFieldStorage(k, x)) for x in v]
32         elif isinstance(v, FileUpload):
33             x = cgi.MiniFieldStorage(k, v.content)
34             x.filename = v.filename
35             form.list.append(x)
36         else:
37             form.list.append(cgi.MiniFieldStorage(k, v))
38     return form
40 class config:
41     TRACKER_NAME = 'testing testing'
42     TRACKER_WEB = 'http://testing.testing/'
44 cm = client.clean_message
45 class MessageTestCase(unittest.TestCase):
46     def testCleanMessageOK(self):
47         self.assertEqual(cm('<br>x<br />'), '<br>x<br />')
48         self.assertEqual(cm('<i>x</i>'), '<i>x</i>')
49         self.assertEqual(cm('<b>x</b>'), '<b>x</b>')
50         self.assertEqual(cm('<a href="y">x</a>'),
51             '<a href="y">x</a>')
52         self.assertEqual(cm('<BR>x<BR />'), '<BR>x<BR />')
53         self.assertEqual(cm('<I>x</I>'), '<I>x</I>')
54         self.assertEqual(cm('<B>x</B>'), '<B>x</B>')
55         self.assertEqual(cm('<A HREF="y">x</A>'),
56             '<A HREF="y">x</A>')
58     def testCleanMessageBAD(self):
59         self.assertEqual(cm('<script>x</script>'),
60             '&lt;script&gt;x&lt;/script&gt;')
61         self.assertEqual(cm('<iframe>x</iframe>'),
62             '&lt;iframe&gt;x&lt;/iframe&gt;')
64 class FormTestCase(unittest.TestCase):
65     def setUp(self):
66         self.dirname = '_test_cgi_form'
67         try:
68             shutil.rmtree(self.dirname)
69         except OSError, error:
70             if error.errno not in (errno.ENOENT, errno.ESRCH): raise
71         # create the instance
72         init.install(self.dirname, 'templates/classic')
73         init.write_select_db(self.dirname, 'anydbm')
74         init.initialise(self.dirname, 'sekrit')
75         
76         # check we can load the package
77         self.instance = instance.open(self.dirname)
78         # and open the database
79         self.db = self.instance.open('admin')
80         self.db.user.create(username='Chef', address='chef@bork.bork.bork',
81             realname='Bork, Chef', roles='User')
82         self.db.user.create(username='mary', address='mary@test',
83             roles='User', realname='Contrary, Mary')
85         test = self.instance.dbinit.Class(self.db, "test",
86             string=hyperdb.String(), number=hyperdb.Number(),
87             boolean=hyperdb.Boolean(), link=hyperdb.Link('test'),
88             multilink=hyperdb.Multilink('test'), date=hyperdb.Date(),
89             interval=hyperdb.Interval())
91         # compile the labels re
92         classes = '|'.join(self.db.classes.keys())
93         self.FV_SPECIAL = re.compile(FormParser.FV_LABELS%classes,
94             re.VERBOSE)
96     def parseForm(self, form, classname='test', nodeid=None):
97         cl = client.Client(self.instance, None, {'PATH_INFO':'/'},
98             makeForm(form))
99         cl.classname = classname
100         cl.nodeid = nodeid
101         cl.db = self.db
102         return cl.parsePropsFromForm()
104     def tearDown(self):
105         self.db.close()
106         try:
107             shutil.rmtree(self.dirname)
108         except OSError, error:
109             if error.errno not in (errno.ENOENT, errno.ESRCH): raise
111     #
112     # form label extraction
113     #
114     def tl(self, s, c, i, a, p):
115         m = self.FV_SPECIAL.match(s)
116         self.assertNotEqual(m, None)
117         d = m.groupdict()
118         self.assertEqual(d['classname'], c)
119         self.assertEqual(d['id'], i)
120         for action in 'required add remove link note file'.split():
121             if a == action:
122                 self.assertNotEqual(d[action], None)
123             else:
124                 self.assertEqual(d[action], None)
125         self.assertEqual(d['propname'], p)
127     def testLabelMatching(self):
128         self.tl('<propname>', None, None, None, '<propname>')
129         self.tl(':required', None, None, 'required', None)
130         self.tl(':confirm:<propname>', None, None, 'confirm', '<propname>')
131         self.tl(':add:<propname>', None, None, 'add', '<propname>')
132         self.tl(':remove:<propname>', None, None, 'remove', '<propname>')
133         self.tl(':link:<propname>', None, None, 'link', '<propname>')
134         self.tl('test1:<prop>', 'test', '1', None, '<prop>')
135         self.tl('test1:required', 'test', '1', 'required', None)
136         self.tl('test1:add:<prop>', 'test', '1', 'add', '<prop>')
137         self.tl('test1:remove:<prop>', 'test', '1', 'remove', '<prop>')
138         self.tl('test1:link:<prop>', 'test', '1', 'link', '<prop>')
139         self.tl('test1:confirm:<prop>', 'test', '1', 'confirm', '<prop>')
140         self.tl('test-1:<prop>', 'test', '-1', None, '<prop>')
141         self.tl('test-1:required', 'test', '-1', 'required', None)
142         self.tl('test-1:add:<prop>', 'test', '-1', 'add', '<prop>')
143         self.tl('test-1:remove:<prop>', 'test', '-1', 'remove', '<prop>')
144         self.tl('test-1:link:<prop>', 'test', '-1', 'link', '<prop>')
145         self.tl('test-1:confirm:<prop>', 'test', '-1', 'confirm', '<prop>')
146         self.tl(':note', None, None, 'note', None)
147         self.tl(':file', None, None, 'file', None)
149     #
150     # Empty form
151     #
152     def testNothing(self):
153         self.assertEqual(self.parseForm({}), ({('test', None): {}}, []))
155     def testNothingWithRequired(self):
156         self.assertRaises(FormError, self.parseForm, {':required': 'string'})
157         self.assertRaises(FormError, self.parseForm,
158             {':required': 'title,status', 'status':'1'}, 'issue')
159         self.assertRaises(FormError, self.parseForm,
160             {':required': ['title','status'], 'status':'1'}, 'issue')
161         self.assertRaises(FormError, self.parseForm,
162             {':required': 'status', 'status':''}, 'issue')
163         self.assertRaises(FormError, self.parseForm,
164             {':required': 'nosy', 'nosy':''}, 'issue')
166     #
167     # Nonexistant edit
168     #
169     def testEditNonexistant(self):
170         self.assertRaises(FormError, self.parseForm, {'boolean': ''},
171             'test', '1')
173     #
174     # String
175     #
176     def testEmptyString(self):
177         self.assertEqual(self.parseForm({'string': ''}),
178             ({('test', None): {}}, []))
179         self.assertEqual(self.parseForm({'string': ' '}),
180             ({('test', None): {}}, []))
181         self.assertRaises(FormError, self.parseForm, {'string': ['', '']})
183     def testSetString(self):
184         self.assertEqual(self.parseForm({'string': 'foo'}),
185             ({('test', None): {'string': 'foo'}}, []))
186         self.assertEqual(self.parseForm({'string': 'a\r\nb\r\n'}),
187             ({('test', None): {'string': 'a\nb'}}, []))
188         nodeid = self.db.issue.create(title='foo')
189         self.assertEqual(self.parseForm({'title': 'foo'}, 'issue', nodeid),
190             ({('issue', nodeid): {}}, []))
192     def testEmptyStringSet(self):
193         nodeid = self.db.issue.create(title='foo')
194         self.assertEqual(self.parseForm({'title': ''}, 'issue', nodeid),
195             ({('issue', nodeid): {'title': None}}, []))
196         nodeid = self.db.issue.create(title='foo')
197         self.assertEqual(self.parseForm({'title': ' '}, 'issue', nodeid),
198             ({('issue', nodeid): {'title': None}}, []))
200     def testFileUpload(self):
201         file = FileUpload('foo', 'foo.txt')
202         self.assertEqual(self.parseForm({'content': file}, 'file'),
203             ({('file', None): {'content': 'foo', 'name': 'foo.txt',
204             'type': 'text/plain'}}, []))
206     def testEditFileClassAttributes(self):
207         self.assertEqual(self.parseForm({'name': 'foo.txt',
208                                          'type': 'application/octet-stream'},
209                                         'file'),
210                          ({('file', None): {'name': 'foo.txt',
211                                             'type': 'application/octet-stream'}},[]))
213     #
214     # Link
215     #
216     def testEmptyLink(self):
217         self.assertEqual(self.parseForm({'link': ''}),
218             ({('test', None): {}}, []))
219         self.assertEqual(self.parseForm({'link': ' '}),
220             ({('test', None): {}}, []))
221         self.assertRaises(FormError, self.parseForm, {'link': ['', '']})
222         self.assertEqual(self.parseForm({'link': '-1'}),
223             ({('test', None): {}}, []))
225     def testSetLink(self):
226         self.assertEqual(self.parseForm({'status': 'unread'}, 'issue'),
227             ({('issue', None): {'status': '1'}}, []))
228         self.assertEqual(self.parseForm({'status': '1'}, 'issue'),
229             ({('issue', None): {'status': '1'}}, []))
230         nodeid = self.db.issue.create(status='unread')
231         self.assertEqual(self.parseForm({'status': 'unread'}, 'issue', nodeid),
232             ({('issue', nodeid): {}}, []))
234     def testUnsetLink(self):
235         nodeid = self.db.issue.create(status='unread')
236         self.assertEqual(self.parseForm({'status': '-1'}, 'issue', nodeid),
237             ({('issue', nodeid): {'status': None}}, []))
239     def testInvalidLinkValue(self):
240 # XXX This is not the current behaviour - should we enforce this?
241 #        self.assertRaises(IndexError, self.parseForm,
242 #            {'status': '4'}))
243         self.assertRaises(FormError, self.parseForm, {'link': 'frozzle'})
244         self.assertRaises(FormError, self.parseForm, {'status': 'frozzle'},
245             'issue')
247     #
248     # Multilink
249     #
250     def testEmptyMultilink(self):
251         self.assertEqual(self.parseForm({'nosy': ''}),
252             ({('test', None): {}}, []))
253         self.assertEqual(self.parseForm({'nosy': ' '}),
254             ({('test', None): {}}, []))
256     def testSetMultilink(self):
257         self.assertEqual(self.parseForm({'nosy': '1'}, 'issue'),
258             ({('issue', None): {'nosy': ['1']}}, []))
259         self.assertEqual(self.parseForm({'nosy': 'admin'}, 'issue'),
260             ({('issue', None): {'nosy': ['1']}}, []))
261         self.assertEqual(self.parseForm({'nosy': ['1','2']}, 'issue'),
262             ({('issue', None): {'nosy': ['1','2']}}, []))
263         self.assertEqual(self.parseForm({'nosy': '1,2'}, 'issue'),
264             ({('issue', None): {'nosy': ['1','2']}}, []))
265         self.assertEqual(self.parseForm({'nosy': 'admin,2'}, 'issue'),
266             ({('issue', None): {'nosy': ['1','2']}}, []))
268     def testMixedMultilink(self):
269         form = cgi.FieldStorage()
270         form.list.append(cgi.MiniFieldStorage('nosy', '1,2'))
271         form.list.append(cgi.MiniFieldStorage('nosy', '3'))
272         cl = client.Client(self.instance, None, {'PATH_INFO':'/'}, form)
273         cl.classname = 'issue'
274         cl.nodeid = None
275         cl.db = self.db
276         self.assertEqual(cl.parsePropsFromForm(), 
277             ({('issue', None): {'nosy': ['1','2', '3']}}, []))
279     def testEmptyMultilinkSet(self):
280         nodeid = self.db.issue.create(nosy=['1','2'])
281         self.assertEqual(self.parseForm({'nosy': ''}, 'issue', nodeid), 
282             ({('issue', nodeid): {'nosy': []}}, []))
283         nodeid = self.db.issue.create(nosy=['1','2'])
284         self.assertEqual(self.parseForm({'nosy': ' '}, 'issue', nodeid), 
285             ({('issue', nodeid): {'nosy': []}}, []))
286         self.assertEqual(self.parseForm({'nosy': '1,2'}, 'issue', nodeid),
287             ({('issue', nodeid): {}}, []))
289     def testInvalidMultilinkValue(self):
290 # XXX This is not the current behaviour - should we enforce this?
291 #        self.assertRaises(IndexError, self.parseForm,
292 #            {'nosy': '4'}))
293         self.assertRaises(FormError, self.parseForm, {'nosy': 'frozzle'},
294             'issue')
295         self.assertRaises(FormError, self.parseForm, {'nosy': '1,frozzle'},
296             'issue')
297         self.assertRaises(FormError, self.parseForm, {'multilink': 'frozzle'})
299     def testMultilinkAdd(self):
300         nodeid = self.db.issue.create(nosy=['1'])
301         # do nothing
302         self.assertEqual(self.parseForm({':add:nosy': ''}, 'issue', nodeid),
303             ({('issue', nodeid): {}}, []))
305         # do something ;)
306         self.assertEqual(self.parseForm({':add:nosy': '2'}, 'issue', nodeid),
307             ({('issue', nodeid): {'nosy': ['1','2']}}, []))
308         self.assertEqual(self.parseForm({':add:nosy': '2,mary'}, 'issue',
309             nodeid), ({('issue', nodeid): {'nosy': ['1','2','4']}}, []))
310         self.assertEqual(self.parseForm({':add:nosy': ['2','3']}, 'issue',
311             nodeid), ({('issue', nodeid): {'nosy': ['1','2','3']}}, []))
313     def testMultilinkAddNew(self):
314         self.assertEqual(self.parseForm({':add:nosy': ['2','3']}, 'issue'),
315             ({('issue', None): {'nosy': ['2','3']}}, []))
317     def testMultilinkRemove(self):
318         nodeid = self.db.issue.create(nosy=['1','2'])
319         # do nothing
320         self.assertEqual(self.parseForm({':remove:nosy': ''}, 'issue', nodeid),
321             ({('issue', nodeid): {}}, []))
323         # do something ;)
324         self.assertEqual(self.parseForm({':remove:nosy': '1'}, 'issue',
325             nodeid), ({('issue', nodeid): {'nosy': ['2']}}, []))
326         self.assertEqual(self.parseForm({':remove:nosy': 'admin,2'},
327             'issue', nodeid), ({('issue', nodeid): {'nosy': []}}, []))
328         self.assertEqual(self.parseForm({':remove:nosy': ['1','2']},
329             'issue', nodeid), ({('issue', nodeid): {'nosy': []}}, []))
331         # add and remove
332         self.assertEqual(self.parseForm({':add:nosy': ['3'],
333             ':remove:nosy': ['1','2']},
334             'issue', nodeid), ({('issue', nodeid): {'nosy': ['3']}}, []))
336         # remove one that doesn't exist?
337         self.assertRaises(FormError, self.parseForm, {':remove:nosy': '4'},
338             'issue', nodeid)
340     def testMultilinkRetired(self):
341         self.db.user.retire('2')
342         self.assertEqual(self.parseForm({'nosy': ['2','3']}, 'issue'),
343             ({('issue', None): {'nosy': ['2','3']}}, []))
344         nodeid = self.db.issue.create(nosy=['1','2'])
345         self.assertEqual(self.parseForm({':remove:nosy': '2'}, 'issue',
346             nodeid), ({('issue', nodeid): {'nosy': ['1']}}, []))
347         self.assertEqual(self.parseForm({':add:nosy': '3'}, 'issue', nodeid),
348             ({('issue', nodeid): {'nosy': ['1','2','3']}}, []))
350     def testAddRemoveNonexistant(self):
351         self.assertRaises(FormError, self.parseForm, {':remove:foo': '2'},
352             'issue')
353         self.assertRaises(FormError, self.parseForm, {':add:foo': '2'},
354             'issue')
356     #
357     # Password
358     #
359     def testEmptyPassword(self):
360         self.assertEqual(self.parseForm({'password': ''}, 'user'),
361             ({('user', None): {}}, []))
362         self.assertEqual(self.parseForm({'password': ''}, 'user'),
363             ({('user', None): {}}, []))
364         self.assertRaises(FormError, self.parseForm, {'password': ['', '']},
365             'user')
366         self.assertRaises(FormError, self.parseForm, {'password': 'foo',
367             ':confirm:password': ['', '']}, 'user')
369     def testSetPassword(self):
370         self.assertEqual(self.parseForm({'password': 'foo',
371             ':confirm:password': 'foo'}, 'user'),
372             ({('user', None): {'password': 'foo'}}, []))
374     def testSetPasswordConfirmBad(self):
375         self.assertRaises(FormError, self.parseForm, {'password': 'foo'},
376             'user')
377         self.assertRaises(FormError, self.parseForm, {'password': 'foo',
378             ':confirm:password': 'bar'}, 'user')
380     def testEmptyPasswordNotSet(self):
381         nodeid = self.db.user.create(username='1',
382             password=password.Password('foo'))
383         self.assertEqual(self.parseForm({'password': ''}, 'user', nodeid),
384             ({('user', nodeid): {}}, []))
385         nodeid = self.db.user.create(username='2',
386             password=password.Password('foo'))
387         self.assertEqual(self.parseForm({'password': '',
388             ':confirm:password': ''}, 'user', nodeid),
389             ({('user', nodeid): {}}, []))
391     #
392     # Boolean
393     #
394     def testEmptyBoolean(self):
395         self.assertEqual(self.parseForm({'boolean': ''}),
396             ({('test', None): {}}, []))
397         self.assertEqual(self.parseForm({'boolean': ' '}),
398             ({('test', None): {}}, []))
399         self.assertRaises(FormError, self.parseForm, {'boolean': ['', '']})
401     def testSetBoolean(self):
402         self.assertEqual(self.parseForm({'boolean': 'yes'}),
403             ({('test', None): {'boolean': 1}}, []))
404         self.assertEqual(self.parseForm({'boolean': 'a\r\nb\r\n'}),
405             ({('test', None): {'boolean': 0}}, []))
406         nodeid = self.db.test.create(boolean=1)
407         self.assertEqual(self.parseForm({'boolean': 'yes'}, 'test', nodeid),
408             ({('test', nodeid): {}}, []))
409         nodeid = self.db.test.create(boolean=0)
410         self.assertEqual(self.parseForm({'boolean': 'no'}, 'test', nodeid),
411             ({('test', nodeid): {}}, []))
413     def testEmptyBooleanSet(self):
414         nodeid = self.db.test.create(boolean=0)
415         self.assertEqual(self.parseForm({'boolean': ''}, 'test', nodeid),
416             ({('test', nodeid): {'boolean': None}}, []))
417         nodeid = self.db.test.create(boolean=1)
418         self.assertEqual(self.parseForm({'boolean': ' '}, 'test', nodeid),
419             ({('test', nodeid): {'boolean': None}}, []))
421     #
422     # Number
423     #
424     def testEmptyNumber(self):
425         self.assertEqual(self.parseForm({'number': ''}),
426             ({('test', None): {}}, []))
427         self.assertEqual(self.parseForm({'number': ' '}),
428             ({('test', None): {}}, []))
429         self.assertRaises(FormError, self.parseForm, {'number': ['', '']})
431     def testInvalidNumber(self):
432         self.assertRaises(FormError, self.parseForm, {'number': 'hi, mum!'})
434     def testSetNumber(self):
435         self.assertEqual(self.parseForm({'number': '1'}),
436             ({('test', None): {'number': 1}}, []))
437         self.assertEqual(self.parseForm({'number': '\n0\n'}),
438             ({('test', None): {'number': 0}}, []))
439         nodeid = self.db.test.create(number=1)
440         self.assertEqual(self.parseForm({'number': '1'}, 'test', nodeid),
441             ({('test', nodeid): {}}, []))
442         nodeid = self.db.test.create(number=0)
443         self.assertEqual(self.parseForm({'number': '0'}, 'test', nodeid),
444             ({('test', nodeid): {}}, []))
446     def testEmptyNumberSet(self):
447         nodeid = self.db.test.create(number=0)
448         self.assertEqual(self.parseForm({'number': ''}, 'test', nodeid),
449             ({('test', nodeid): {'number': None}}, []))
450         nodeid = self.db.test.create(number=1)
451         self.assertEqual(self.parseForm({'number': ' '}, 'test', nodeid),
452             ({('test', nodeid): {'number': None}}, []))
454     #
455     # Date
456     #
457     def testEmptyDate(self):
458         self.assertEqual(self.parseForm({'date': ''}),
459             ({('test', None): {}}, []))
460         self.assertEqual(self.parseForm({'date': ' '}),
461             ({('test', None): {}}, []))
462         self.assertRaises(FormError, self.parseForm, {'date': ['', '']})
464     def testInvalidDate(self):
465         self.assertRaises(FormError, self.parseForm, {'date': '12'})
467     def testSetDate(self):
468         self.assertEqual(self.parseForm({'date': '2003-01-01'}),
469             ({('test', None): {'date': date.Date('2003-01-01')}}, []))
470         nodeid = self.db.test.create(date=date.Date('2003-01-01'))
471         self.assertEqual(self.parseForm({'date': '2003-01-01'}, 'test', 
472             nodeid), ({('test', nodeid): {}}, []))
474     def testEmptyDateSet(self):
475         nodeid = self.db.test.create(date=date.Date('.'))
476         self.assertEqual(self.parseForm({'date': ''}, 'test', nodeid), 
477             ({('test', nodeid): {'date': None}}, []))
478         nodeid = self.db.test.create(date=date.Date('1970-01-01.00:00:00'))
479         self.assertEqual(self.parseForm({'date': ' '}, 'test', nodeid), 
480             ({('test', nodeid): {'date': None}}, []))
482     #
483     # Test multiple items in form
484     #
485     def testMultiple(self):
486         self.assertEqual(self.parseForm({'string': 'a', 'issue-1@title': 'b'}),
487             ({('test', None): {'string': 'a'},
488               ('issue', '-1'): {'title': 'b'}
489              }, []))
491     def testMultipleExistingContext(self):
492         nodeid = self.db.test.create()
493         self.assertEqual(self.parseForm({'string': 'a', 'issue-1@title': 'b'},
494             'test', nodeid),({('test', nodeid): {'string': 'a'},
495             ('issue', '-1'): {'title': 'b'}}, []))
497     def testLinking(self):
498         self.assertEqual(self.parseForm({
499             'string': 'a',
500             'issue-1@add@nosy': '1',
501             'issue-2@link@superseder': 'issue-1',
502             }),
503             ({('test', None): {'string': 'a'},
504               ('issue', '-1'): {'nosy': ['1']},
505               ('issue', '-2'): {}
506              },
507              [('issue', '-2', 'superseder', [('issue', '-1')])
508              ]
509             )
510         )
512     def testLinkBadDesignator(self):
513         self.assertRaises(FormError, self.parseForm,
514             {'test-1@link@link': 'blah'})
515         self.assertRaises(FormError, self.parseForm,
516             {'test-1@link@link': 'issue'})
518     def testLinkNotLink(self):
519         self.assertRaises(FormError, self.parseForm,
520             {'test-1@link@boolean': 'issue-1'})
521         self.assertRaises(FormError, self.parseForm,
522             {'test-1@link@string': 'issue-1'})
524     def testBackwardsCompat(self):
525         res = self.parseForm({':note': 'spam'}, 'issue')
526         date = res[0][('msg', '-1')]['date']
527         self.assertEqual(res, ({('issue', None): {}, ('msg', '-1'):
528             {'content': 'spam', 'author': '1', 'date': date}},
529             [('issue', None, 'messages', [('msg', '-1')])]))
530         file = FileUpload('foo', 'foo.txt')
531         self.assertEqual(self.parseForm({':file': file}, 'issue'),
532             ({('issue', None): {}, ('file', '-1'): {'content': 'foo',
533             'name': 'foo.txt', 'type': 'text/plain'}},
534             [('issue', None, 'files', [('file', '-1')])]))
536 def test_suite():
537     suite = unittest.TestSuite()
538     suite.addTest(unittest.makeSuite(FormTestCase))
539     suite.addTest(unittest.makeSuite(MessageTestCase))
540     return suite
542 if __name__ == '__main__':
543     runner = unittest.TextTestRunner()
544     unittest.main(testRunner=runner)
546 # vim: set filetype=python ts=4 sw=4 et si