diff test/db_test_base.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 f015df5f8edf
children 1303ba0b5358
line wrap: on
line diff
--- a/test/db_test_base.py	Wed Oct 25 23:07:35 2017 -0400
+++ b/test/db_test_base.py	Mon Nov 06 09:26:59 2017 +0100
@@ -16,7 +16,7 @@
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 
 import unittest, os, shutil, errno, imp, sys, time, pprint, base64, os.path
-import logging
+import logging, cgi
 import gpgmelib
 from email.parser import FeedParser
 
@@ -27,6 +27,11 @@
 from roundup import date, password, init, instance, configuration, \
     roundupdb, i18n, hyperdb
 from roundup.cgi.templating import HTMLItem
+from roundup.cgi.templating import HTMLProperty, _HTMLItem, anti_csrf_nonce
+from roundup.cgi import client, actions
+from roundup.cgi.engine_zopetal import RoundupPageTemplate
+from roundup.cgi.templating import HTMLItem
+from roundup.exceptions import UsageError, Reject
 
 from mocknull import MockNull
 
@@ -2295,7 +2300,6 @@
     # test props from args parsing
     def testAdminOtherCommands(self):
         import roundup.admin
-        from roundup.exceptions import UsageError
 
         # use the filtering setup to create a bunch of items
         ae, dummy1, dummy2 = self.filteringSetup()
@@ -3125,4 +3129,189 @@
         ae(bool(issue ['assignedto']['username']),False)
         ae(bool(issue ['priority']['name']),False)
 
+def makeForm(args):
+    form = cgi.FieldStorage()
+    for k,v in args.items():
+        if type(v) is type([]):
+            [form.list.append(cgi.MiniFieldStorage(k, x)) for x in v]
+        elif isinstance(v, FileUpload):
+            x = cgi.MiniFieldStorage(k, v.content)
+            x.filename = v.filename
+            form.list.append(x)
+        else:
+            form.list.append(cgi.MiniFieldStorage(k, v))
+    return form
+
+class FileUpload:
+    def __init__(self, content, filename):
+        self.content = content
+        self.filename = filename
+
+class FormTestParent(object):
+
+    backend = "anydbm"
+    def setupDetectors(self):
+        pass
+
+    def setUp(self):
+        self.dirname = '_test_cgi_form'
+        # set up and open a tracker
+        self.instance = setupTracker(self.dirname, backend = self.backend)
+
+        # We may want to register separate detectors
+        self.setupDetectors()
+
+        # open the database
+        self.db = self.instance.open('admin')
+        self.db.tx_Source = "web"
+        self.db.user.create(username='Chef', address='chef@bork.bork.bork',
+            realname='Bork, Chef', roles='User')
+        self.db.user.create(username='mary', address='mary@test.test',
+            roles='User', realname='Contrary, Mary')
+
+        self.db.issue.addprop(tx_Source=hyperdb.String())
+        self.db.msg.addprop(tx_Source=hyperdb.String())
+        self.db.post_init()
+
+    def setupClient(self, form, classname, nodeid=None, template='item', env_addon=None):
+        cl = client.Client(self.instance, None, {'PATH_INFO':'/',
+            'REQUEST_METHOD':'POST'}, makeForm(form))
+        cl.classname = classname
+        cl.base = 'http://whoami.com/path/'
+        cl.nodeid = nodeid
+        cl.language = ('en',)
+        cl.userid = '1'
+        cl.db = self.db
+        cl.user = 'admin'
+        cl.template = template
+        if env_addon is not None:
+            cl.env.update(env_addon)
+        return cl
+
+    def parseForm(self, form, classname='test', nodeid=None):
+        cl = self.setupClient(form, classname, nodeid)
+        return cl.parsePropsFromForm(create=1)
+
+    def tearDown(self):
+        self.db.close()
+        try:
+            shutil.rmtree(self.dirname)
+        except OSError as error:
+            if error.errno not in (errno.ENOENT, errno.ESRCH): raise
+
+class SpecialAction(actions.EditItemAction):
+    x = False
+    def handle(self):
+        self.__class__.x = True
+        cl = self.db.getclass(self.classname)
+        cl.set(self.nodeid, status='2')
+        cl.set(self.nodeid, title="Just a test")
+        assert(0, "not reached")
+        self.db.commit()
+
+def reject_title(db, cl, nodeid, newvalues):
+    if 'title' in newvalues:
+        raise Reject ("REJECT TITLE CHANGE")
+
+def init_reject(db):
+    db.issue.audit("set", reject_title)
+
+def get_extensions(self, what):
+    """ For monkey-patch of instance.get_extensions: The old method is
+        kept as _get_extensions, we use the new method to return our own
+        auditors/reactors.
+    """
+    if what == 'detectors':
+        return [init_reject]
+    return self._get_extensions(what)
+
+class SpecialActionTest(FormTestParent):
+
+    def setupDetectors(self):
+        self.instance._get_extensions = self.instance.get_extensions
+        def ge(what):
+            return get_extensions(self.instance, what)
+        self.instance.get_extensions = ge
+
+    def setUp(self):
+        FormTestParent.setUp(self)
+
+        self.instance.registerAction('special', SpecialAction)
+        self.issue = self.db.issue.create (title = "hello", status='1')
+        self.db.commit ()
+        if not os.environ.has_key('SENDMAILDEBUG'):
+            os.environ['SENDMAILDEBUG'] = 'mail-test2.log'
+        self.SENDMAILDEBUG = os.environ['SENDMAILDEBUG']
+        page_template = """
+        <html>
+         <body>
+          <p tal:condition="options/error_message|nothing"
+             tal:repeat="m options/error_message"
+             tal:content="structure m"/>
+          <p tal:content="context/title/plain"/>
+          <p tal:content="context/status/plain"/>
+          <p tal:content="structure context/submit"/>
+         </body>
+        </html>
+        """.strip ()
+        self.form = {':action': 'special'}
+        cl = self.setupClient(self.form, 'issue', self.issue)
+        pt = RoundupPageTemplate()
+        pt.pt_edit(page_template, 'text/html')
+        self.out = []
+        def wh(s):
+            self.out.append(s)
+        cl.write_html = wh
+        def load_template(x):
+            return pt
+        cl.instance.templates.load = load_template
+        cl.selectTemplate = MockNull()
+        cl.determine_context = MockNull ()
+        def hasPermission(s, p, classname=None, d=None, e=None, **kw):
+            return True
+        self.hasPermission = actions.Action.hasPermission
+        actions.Action.hasPermission = hasPermission
+        self.e1 = _HTMLItem.is_edit_ok
+        _HTMLItem.is_edit_ok = lambda x : True
+        self.e2 = HTMLProperty.is_edit_ok
+        HTMLProperty.is_edit_ok = lambda x : True
+        # Make sure header check passes
+        cl.env['HTTP_REFERER'] = 'http://whoami.com/path/'
+        self.client = cl
+
+    def tearDown(self):
+        FormTestParent.tearDown(self)
+        # Remove monkey-patches
+        self.instance.get_extensions = self.instance._get_extensions
+        del self.instance._get_extensions
+        actions.Action.hasPermission = self.hasPermission
+        _HTMLItem.is_edit_ok = self.e1
+        HTMLProperty.is_edit_ok = self.e2
+        if os.path.exists(self.SENDMAILDEBUG):
+            #os.remove(self.SENDMAILDEBUG)
+            pass
+
+    def testInnerMain(self):
+        cl = self.client
+        cl.session_api = MockNull(_sid="1234567890")
+        self.form ['@nonce'] = anti_csrf_nonce(cl, cl)
+        cl.form = makeForm(self.form)
+        # inner_main will re-open the database!
+        # Note that in the template above, the rendering of the
+        # context/submit button will also call anti_csrf_nonce which
+        # does a commit of the otk to the database.
+        cl.inner_main()
+        cl.db.close()
+        print self.out
+        # Make sure the action was called
+        self.assertEqual(SpecialAction.x, True)
+        # Check that the Reject worked:
+        self.assertNotEqual(-1, self.out[0].index('REJECT TITLE CHANGE'))
+        # Re-open db
+        self.db.close()
+        self.db = self.instance.open ('admin')
+        # We shouldn't see any changes
+        self.assertEqual(self.db.issue.get(self.issue, 'title'), 'hello')
+        self.assertEqual(self.db.issue.get(self.issue, 'status'), '1')
+
 # vim: set et sts=4 sw=4 :

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