Mercurial > p > roundup > code
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") |
