comparison test/test_cgi.py @ 5310:efb34cbdba7c

Add (currently failing) test for atomic actions Actions via the web-interface can have several database-modifying instructions. These should be atomic, i.e., either all should go in or none. This is currently not the case due to commit calls from OTK handling (CSRF-protection).
author Ralf Schlatterbeck <rsc@runtux.com>
date Mon, 06 Nov 2017 09:26:59 +0100
parents 198b6e810c67
children 351763d6400a
comparison
equal deleted inserted replaced
5309:20084e2b48c3 5310:efb34cbdba7c
11 import unittest, os, shutil, errno, sys, difflib, cgi, re, StringIO 11 import unittest, os, shutil, errno, sys, difflib, cgi, re, StringIO
12 12
13 from roundup.cgi import client, actions, exceptions 13 from roundup.cgi import client, actions, exceptions
14 from roundup.cgi.exceptions import FormError, NotFound 14 from roundup.cgi.exceptions import FormError, NotFound
15 from roundup.exceptions import UsageError 15 from roundup.exceptions import UsageError
16 from roundup.cgi.templating import HTMLItem, HTMLRequest, NoTemplate, anti_csrf_nonce 16 from roundup.cgi.templating import HTMLItem, HTMLRequest, NoTemplate
17 from roundup.cgi.templating import HTMLProperty, _HTMLItem 17 from roundup.cgi.templating import HTMLProperty, _HTMLItem, anti_csrf_nonce
18 from roundup.cgi.form_parser import FormParser 18 from roundup.cgi.form_parser import FormParser
19 from roundup import init, instance, password, hyperdb, date 19 from roundup import init, instance, password, hyperdb, date
20 20
21 # For testing very simple rendering 21 # For testing very simple rendering
22 from roundup.cgi.engine_zopetal import RoundupPageTemplate 22 from roundup.cgi.engine_zopetal import RoundupPageTemplate
23 23
24 from mocknull import MockNull 24 from mocknull import MockNull
25 25
26 import db_test_base 26 import db_test_base
27 27 from db_test_base import FormTestParent, setupTracker, FileUpload
28 class FileUpload:
29 def __init__(self, content, filename):
30 self.content = content
31 self.filename = filename
32 28
33 class FileList: 29 class FileList:
34 def __init__(self, name, *files): 30 def __init__(self, name, *files):
35 self.name = name 31 self.name = name
36 self.files = files 32 self.files = files
37 def items (self): 33 def items (self):
38 for f in self.files: 34 for f in self.files:
39 yield (self.name, f) 35 yield (self.name, f)
40
41 def makeForm(args):
42 form = cgi.FieldStorage()
43 for k,v in args.items():
44 if type(v) is type([]):
45 [form.list.append(cgi.MiniFieldStorage(k, x)) for x in v]
46 elif isinstance(v, FileUpload):
47 x = cgi.MiniFieldStorage(k, v.content)
48 x.filename = v.filename
49 form.list.append(x)
50 else:
51 form.list.append(cgi.MiniFieldStorage(k, v))
52 return form
53 36
54 cm = client.add_message 37 cm = client.add_message
55 class MessageTestCase(unittest.TestCase): 38 class MessageTestCase(unittest.TestCase):
56 # Note: Escaping is now handled on a message-by-message basis at a 39 # Note: Escaping is now handled on a message-by-message basis at a
57 # point where we still know what generates a message. In this way we 40 # point where we still know what generates a message. In this way we
84 def testAddMessageNoEscape(self): 67 def testAddMessageNoEscape(self):
85 self.assertEqual(cm([],'<i>x</i>',False), ['<i>x</i>']) 68 self.assertEqual(cm([],'<i>x</i>',False), ['<i>x</i>'])
86 self.assertEqual(cm([],'<i>x</i>\n<b>x</b>',False), 69 self.assertEqual(cm([],'<i>x</i>\n<b>x</b>',False),
87 ['<i>x</i><br />\n<b>x</b>']) 70 ['<i>x</i><br />\n<b>x</b>'])
88 71
89 class FormTestCase(unittest.TestCase): 72 class FormTestCase(FormTestParent, unittest.TestCase):
73
90 def setUp(self): 74 def setUp(self):
91 self.dirname = '_test_cgi_form' 75 FormTestParent.setUp(self)
92 # set up and open a tracker
93 self.instance = db_test_base.setupTracker(self.dirname)
94
95 # open the database
96 self.db = self.instance.open('admin')
97 self.db.tx_Source = "web"
98 self.db.user.create(username='Chef', address='chef@bork.bork.bork',
99 realname='Bork, Chef', roles='User')
100 self.db.user.create(username='mary', address='mary@test.test',
101 roles='User', realname='Contrary, Mary')
102
103 self.db.issue.addprop(tx_Source=hyperdb.String())
104 self.db.msg.addprop(tx_Source=hyperdb.String())
105
106 self.db.post_init()
107 76
108 vars = {} 77 vars = {}
109 thisdir = os.path.dirname(__file__) 78 thisdir = os.path.dirname(__file__)
110 execfile(os.path.join(thisdir, "tx_Source_detector.py"), vars) 79 execfile(os.path.join(thisdir, "tx_Source_detector.py"), vars)
111 vars['init'](self.db) 80 vars['init'](self.db)
119 88
120 # compile the labels re 89 # compile the labels re
121 classes = '|'.join(self.db.classes.keys()) 90 classes = '|'.join(self.db.classes.keys())
122 self.FV_SPECIAL = re.compile(FormParser.FV_LABELS%classes, 91 self.FV_SPECIAL = re.compile(FormParser.FV_LABELS%classes,
123 re.VERBOSE) 92 re.VERBOSE)
124
125 def setupClient(self, form, classname, nodeid=None, template='item', env_addon=None):
126 cl = client.Client(self.instance, None, {'PATH_INFO':'/',
127 'REQUEST_METHOD':'POST'}, makeForm(form))
128 cl.classname = classname
129 cl.base = 'http://whoami.com/path/'
130 cl.nodeid = nodeid
131 cl.language = ('en',)
132 cl.userid = '1'
133 cl.db = self.db
134 cl.user = 'admin'
135 cl.template = template
136 if env_addon is not None:
137 cl.env.update(env_addon)
138 return cl
139
140 def parseForm(self, form, classname='test', nodeid=None):
141 cl = self.setupClient(form, classname, nodeid)
142 return cl.parsePropsFromForm(create=1)
143
144 def tearDown(self):
145 self.db.close()
146 try:
147 shutil.rmtree(self.dirname)
148 except OSError as error:
149 if error.errno not in (errno.ENOENT, errno.ESRCH): raise
150 93
151 # 94 #
152 # form label extraction 95 # form label extraction
153 # 96 #
154 def tl(self, s, c, i, a, p): 97 def tl(self, s, c, i, a, p):
994 937
995 import copy 938 import copy
996 form2 = copy.copy(form) 939 form2 = copy.copy(form)
997 form2.update({'@csrf': 'booogus'}) 940 form2.update({'@csrf': 'booogus'})
998 # add a bogus csrf field to the form and rerun the inner_main 941 # add a bogus csrf field to the form and rerun the inner_main
999 cl.form = makeForm(form2) 942 cl.form = db_test_base.makeForm(form2)
1000 943
1001 cl.inner_main() 944 cl.inner_main()
1002 match_at=out[0].find('Invalid csrf token found: booogus') 945 match_at=out[0].find('Invalid csrf token found: booogus')
1003 print "result of subtest 7:", out[0] 946 print "result of subtest 7:", out[0]
1004 self.assertEqual(match_at, 36) 947 self.assertEqual(match_at, 36)
1014 otks.get(nonce, 'session', default=None) 957 otks.get(nonce, 'session', default=None)
1015 self.assertEqual(isitthere, True) 958 self.assertEqual(isitthere, True)
1016 959
1017 form2.update({'@csrf': nonce}) 960 form2.update({'@csrf': nonce})
1018 # add a real csrf field to the form and rerun the inner_main 961 # add a real csrf field to the form and rerun the inner_main
1019 cl.form = makeForm(form2) 962 cl.form = db_test_base.makeForm(form2)
1020 cl.inner_main() 963 cl.inner_main()
1021 # csrf passes and redirects to the new issue. 964 # csrf passes and redirects to the new issue.
1022 match_at=out[0].find('Redirecting to <a href="http://whoami.com/path/issue1?@ok_message') 965 match_at=out[0].find('Redirecting to <a href="http://whoami.com/path/issue1?@ok_message')
1023 print "result of subtest 9:", out[0] 966 print "result of subtest 9:", out[0]
1024 self.assertEqual(match_at, 0) 967 self.assertEqual(match_at, 0)
1038 cl.env['HTTP_REFERER'] = 'http://whoami.com/path/' 981 cl.env['HTTP_REFERER'] = 'http://whoami.com/path/'
1039 form2 = copy.copy(form) 982 form2 = copy.copy(form)
1040 nonce = anti_csrf_nonce(cl, cl) 983 nonce = anti_csrf_nonce(cl, cl)
1041 form2.update({'@csrf': nonce}) 984 form2.update({'@csrf': nonce})
1042 # add a real csrf field to the form and rerun the inner_main 985 # add a real csrf field to the form and rerun the inner_main
1043 cl.form = makeForm(form2) 986 cl.form = db_test_base.makeForm(form2)
1044 cl.inner_main() 987 cl.inner_main()
1045 # csrf passes but fail creating new issue because not a post 988 # csrf passes but fail creating new issue because not a post
1046 match_at=out[0].find('<p>Invalid request</p>') 989 match_at=out[0].find('<p>Invalid request</p>')
1047 print "result of subtest 11:", out[0] 990 print "result of subtest 11:", out[0]
1048 self.assertEqual(match_at, 33) 991 self.assertEqual(match_at, 33)
1135 # 1078 #
1136 # XXX test all default permissions 1079 # XXX test all default permissions
1137 def _make_client(self, form, classname='user', nodeid='1', 1080 def _make_client(self, form, classname='user', nodeid='1',
1138 userid='2', template='item'): 1081 userid='2', template='item'):
1139 cl = client.Client(self.instance, None, {'PATH_INFO':'/', 1082 cl = client.Client(self.instance, None, {'PATH_INFO':'/',
1140 'REQUEST_METHOD':'POST'}, makeForm(form)) 1083 'REQUEST_METHOD':'POST'}, db_test_base.makeForm(form))
1141 cl.classname = classname 1084 cl.classname = classname
1142 if nodeid is not None: 1085 if nodeid is not None:
1143 cl.nodeid = nodeid 1086 cl.nodeid = nodeid
1144 cl.db = self.db 1087 cl.db = self.db
1145 cl.userid = userid 1088 cl.userid = userid
1560 class TemplateHtmlRendering(unittest.TestCase): 1503 class TemplateHtmlRendering(unittest.TestCase):
1561 ''' try to test the rendering code for tal ''' 1504 ''' try to test the rendering code for tal '''
1562 def setUp(self): 1505 def setUp(self):
1563 self.dirname = '_test_template' 1506 self.dirname = '_test_template'
1564 # set up and open a tracker 1507 # set up and open a tracker
1565 self.instance = db_test_base.setupTracker(self.dirname) 1508 self.instance = setupTracker(self.dirname)
1566 1509
1567 # open the database 1510 # open the database
1568 self.db = self.instance.open('admin') 1511 self.db = self.instance.open('admin')
1569 self.db.tx_Source = "web" 1512 self.db.tx_Source = "web"
1570 self.db.user.create(username='Chef', address='chef@bork.bork.bork', 1513 self.db.user.create(username='Chef', address='chef@bork.bork.bork',
1574 self.db.post_init() 1517 self.db.post_init()
1575 1518
1576 # create a client instance and hijack write_html 1519 # create a client instance and hijack write_html
1577 self.client = client.Client(self.instance, "user", 1520 self.client = client.Client(self.instance, "user",
1578 {'PATH_INFO':'/user', 'REQUEST_METHOD':'POST'}, 1521 {'PATH_INFO':'/user', 'REQUEST_METHOD':'POST'},
1579 form=makeForm({"@template": "item"})) 1522 form=db_test_base.makeForm({"@template": "item"}))
1580 1523
1581 self.client._error_message = [] 1524 self.client._error_message = []
1582 self.client._ok_message = [] 1525 self.client._ok_message = []
1583 self.client.db = self.db 1526 self.client.db = self.db
1584 self.client.userid = '1' 1527 self.client.userid = '1'
1622 # run determine_context to set the required client attributes 1565 # run determine_context to set the required client attributes
1623 # run renderContext(); check result for proper page 1566 # run renderContext(); check result for proper page
1624 1567
1625 # this will generate the default home page like 1568 # this will generate the default home page like
1626 # testrenderFrontPage 1569 # testrenderFrontPage
1627 self.client.form=makeForm({}) 1570 self.client.form=db_test_base.makeForm({})
1628 self.client.path = '' 1571 self.client.path = ''
1629 self.client.determine_context() 1572 self.client.determine_context()
1630 self.assertEqual((self.client.classname, self.client.template, self.client.nodeid), (None, '', None)) 1573 self.assertEqual((self.client.classname, self.client.template, self.client.nodeid), (None, '', None))
1631 self.assertEqual(self.client._ok_message, []) 1574 self.assertEqual(self.client._ok_message, [])
1632 1575
1633 result = self.client.renderContext() 1576 result = self.client.renderContext()
1634 self.assertNotEqual(-1, 1577 self.assertNotEqual(-1,
1635 result.index('<!-- SHA: c87a4e18d59a527331f1d367c0c6cc67ee123e63 -->')) 1578 result.index('<!-- SHA: c87a4e18d59a527331f1d367c0c6cc67ee123e63 -->'))
1636 1579
1637 # now look at the user index page 1580 # now look at the user index page
1638 self.client.form=makeForm({ "@ok_message": "ok message", "@template": "index"}) 1581 self.client.form=db_test_base.makeForm(
1582 { "@ok_message": "ok message", "@template": "index"})
1639 self.client.path = 'user' 1583 self.client.path = 'user'
1640 self.client.determine_context() 1584 self.client.determine_context()
1641 self.assertEqual((self.client.classname, self.client.template, self.client.nodeid), ('user', 'index', None)) 1585 self.assertEqual((self.client.classname, self.client.template, self.client.nodeid), ('user', 'index', None))
1642 self.assertEqual(self.client._ok_message, ['ok message']) 1586 self.assertEqual(self.client._ok_message, ['ok message'])
1643 1587
1653 # set up the client; 1597 # set up the client;
1654 # run determine_context to set the required client attributes 1598 # run determine_context to set the required client attributes
1655 # run renderContext(); check result for proper page 1599 # run renderContext(); check result for proper page
1656 1600
1657 # Test ok state template that uses user.forgotten.html 1601 # Test ok state template that uses user.forgotten.html
1658 self.client.form=makeForm({"@template": "forgotten|item"}) 1602 self.client.form=db_test_base.makeForm({"@template": "forgotten|item"})
1659 self.client.path = 'user' 1603 self.client.path = 'user'
1660 self.client.determine_context() 1604 self.client.determine_context()
1661 self.client.session_api = MockNull(_sid="1234567890") 1605 self.client.session_api = MockNull(_sid="1234567890")
1662 self.assertEqual((self.client.classname, self.client.template, self.client.nodeid), ('user', 'forgotten|item', None)) 1606 self.assertEqual((self.client.classname, self.client.template, self.client.nodeid), ('user', 'forgotten|item', None))
1663 self.assertEqual(self.client._ok_message, []) 1607 self.assertEqual(self.client._ok_message, [])
1667 # sha1sum of classic tracker user.forgotten.template must be found 1611 # sha1sum of classic tracker user.forgotten.template must be found
1668 self.assertNotEqual(-1, 1612 self.assertNotEqual(-1,
1669 result.index('<!-- SHA: eb5dd0bec7a57d58cb7edbeb939fb0390ed1bf74 -->')) 1613 result.index('<!-- SHA: eb5dd0bec7a57d58cb7edbeb939fb0390ed1bf74 -->'))
1670 1614
1671 # now set an error in the form to get error template user.item.html 1615 # now set an error in the form to get error template user.item.html
1672 self.client.form=makeForm({"@template": "forgotten|item", 1616 self.client.form=db_test_base.makeForm({"@template": "forgotten|item",
1673 "@error_message": "this is an error"}) 1617 "@error_message": "this is an error"})
1674 self.client.path = 'user' 1618 self.client.path = 'user'
1675 self.client.determine_context() 1619 self.client.determine_context()
1676 self.assertEqual((self.client.classname, self.client.template, self.client.nodeid), ('user', 'forgotten|item', None)) 1620 self.assertEqual((self.client.classname, self.client.template, self.client.nodeid), ('user', 'forgotten|item', None))
1677 self.assertEqual(self.client._ok_message, []) 1621 self.assertEqual(self.client._ok_message, [])
1771 ''' Test the template resolving code, i.e. what can be given to @template 1715 ''' Test the template resolving code, i.e. what can be given to @template
1772 ''' 1716 '''
1773 def setUp(self): 1717 def setUp(self):
1774 self.dirname = '_test_template' 1718 self.dirname = '_test_template'
1775 # set up and open a tracker 1719 # set up and open a tracker
1776 self.instance = db_test_base.setupTracker(self.dirname) 1720 self.instance = setupTracker(self.dirname)
1777 1721
1778 # open the database 1722 # open the database
1779 self.db = self.instance.open('admin') 1723 self.db = self.instance.open('admin')
1780 self.db.tx_Source = "web" 1724 self.db.tx_Source = "web"
1781 self.db.user.create(username='Chef', address='chef@bork.bork.bork', 1725 self.db.user.create(username='Chef', address='chef@bork.bork.bork',
1800 1744
1801 # get the client instance The form is needed to initialize, 1745 # get the client instance The form is needed to initialize,
1802 # but not used since I call selectTemplate directly. 1746 # but not used since I call selectTemplate directly.
1803 t = client.Client(self.instance, "user", 1747 t = client.Client(self.instance, "user",
1804 {'PATH_INFO':'/user', 'REQUEST_METHOD':'POST'}, 1748 {'PATH_INFO':'/user', 'REQUEST_METHOD':'POST'},
1805 form=makeForm({"@template": "item"})) 1749 form=db_test_base.makeForm({"@template": "item"}))
1806 1750
1807 # create new file in subdir and a dummy file outside of 1751 # create new file in subdir and a dummy file outside of
1808 # the tracker's html subdirectory 1752 # the tracker's html subdirectory
1809 shutil.copyfile(self.dirname + "/html/issue.item.html", 1753 shutil.copyfile(self.dirname + "/html/issue.item.html",
1810 subdir + "/issue.item.html") 1754 subdir + "/issue.item.html")

Roundup Issue Tracker: http://roundup-tracker.org/