Skip to content

Commit 58aceff

Browse files
committed
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).
1 parent aafd489 commit 58aceff

File tree

6 files changed

+246
-79
lines changed

6 files changed

+246
-79
lines changed

test/db_test_base.py

Lines changed: 191 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717

1818
import unittest, os, shutil, errno, imp, sys, time, pprint, base64, os.path
19-
import logging
19+
import logging, cgi
2020
import gpgmelib
2121
from email.parser import FeedParser
2222

@@ -27,6 +27,11 @@
2727
from roundup import date, password, init, instance, configuration, \
2828
roundupdb, i18n, hyperdb
2929
from roundup.cgi.templating import HTMLItem
30+
from roundup.cgi.templating import HTMLProperty, _HTMLItem, anti_csrf_nonce
31+
from roundup.cgi import client, actions
32+
from roundup.cgi.engine_zopetal import RoundupPageTemplate
33+
from roundup.cgi.templating import HTMLItem
34+
from roundup.exceptions import UsageError, Reject
3035

3136
from mocknull import MockNull
3237

@@ -2295,7 +2300,6 @@ def stderrwrite(s):
22952300
# test props from args parsing
22962301
def testAdminOtherCommands(self):
22972302
import roundup.admin
2298-
from roundup.exceptions import UsageError
22992303

23002304
# use the filtering setup to create a bunch of items
23012305
ae, dummy1, dummy2 = self.filteringSetup()
@@ -3125,4 +3129,189 @@ def testHTMLItemDerefFail(self):
31253129
ae(bool(issue ['assignedto']['username']),False)
31263130
ae(bool(issue ['priority']['name']),False)
31273131

3132+
def makeForm(args):
3133+
form = cgi.FieldStorage()
3134+
for k,v in args.items():
3135+
if type(v) is type([]):
3136+
[form.list.append(cgi.MiniFieldStorage(k, x)) for x in v]
3137+
elif isinstance(v, FileUpload):
3138+
x = cgi.MiniFieldStorage(k, v.content)
3139+
x.filename = v.filename
3140+
form.list.append(x)
3141+
else:
3142+
form.list.append(cgi.MiniFieldStorage(k, v))
3143+
return form
3144+
3145+
class FileUpload:
3146+
def __init__(self, content, filename):
3147+
self.content = content
3148+
self.filename = filename
3149+
3150+
class FormTestParent(object):
3151+
3152+
backend = "anydbm"
3153+
def setupDetectors(self):
3154+
pass
3155+
3156+
def setUp(self):
3157+
self.dirname = '_test_cgi_form'
3158+
# set up and open a tracker
3159+
self.instance = setupTracker(self.dirname, backend = self.backend)
3160+
3161+
# We may want to register separate detectors
3162+
self.setupDetectors()
3163+
3164+
# open the database
3165+
self.db = self.instance.open('admin')
3166+
self.db.tx_Source = "web"
3167+
self.db.user.create(username='Chef', address='[email protected]',
3168+
realname='Bork, Chef', roles='User')
3169+
self.db.user.create(username='mary', address='[email protected]',
3170+
roles='User', realname='Contrary, Mary')
3171+
3172+
self.db.issue.addprop(tx_Source=hyperdb.String())
3173+
self.db.msg.addprop(tx_Source=hyperdb.String())
3174+
self.db.post_init()
3175+
3176+
def setupClient(self, form, classname, nodeid=None, template='item', env_addon=None):
3177+
cl = client.Client(self.instance, None, {'PATH_INFO':'/',
3178+
'REQUEST_METHOD':'POST'}, makeForm(form))
3179+
cl.classname = classname
3180+
cl.base = 'http://whoami.com/path/'
3181+
cl.nodeid = nodeid
3182+
cl.language = ('en',)
3183+
cl.userid = '1'
3184+
cl.db = self.db
3185+
cl.user = 'admin'
3186+
cl.template = template
3187+
if env_addon is not None:
3188+
cl.env.update(env_addon)
3189+
return cl
3190+
3191+
def parseForm(self, form, classname='test', nodeid=None):
3192+
cl = self.setupClient(form, classname, nodeid)
3193+
return cl.parsePropsFromForm(create=1)
3194+
3195+
def tearDown(self):
3196+
self.db.close()
3197+
try:
3198+
shutil.rmtree(self.dirname)
3199+
except OSError as error:
3200+
if error.errno not in (errno.ENOENT, errno.ESRCH): raise
3201+
3202+
class SpecialAction(actions.EditItemAction):
3203+
x = False
3204+
def handle(self):
3205+
self.__class__.x = True
3206+
cl = self.db.getclass(self.classname)
3207+
cl.set(self.nodeid, status='2')
3208+
cl.set(self.nodeid, title="Just a test")
3209+
assert(0, "not reached")
3210+
self.db.commit()
3211+
3212+
def reject_title(db, cl, nodeid, newvalues):
3213+
if 'title' in newvalues:
3214+
raise Reject ("REJECT TITLE CHANGE")
3215+
3216+
def init_reject(db):
3217+
db.issue.audit("set", reject_title)
3218+
3219+
def get_extensions(self, what):
3220+
""" For monkey-patch of instance.get_extensions: The old method is
3221+
kept as _get_extensions, we use the new method to return our own
3222+
auditors/reactors.
3223+
"""
3224+
if what == 'detectors':
3225+
return [init_reject]
3226+
return self._get_extensions(what)
3227+
3228+
class SpecialActionTest(FormTestParent):
3229+
3230+
def setupDetectors(self):
3231+
self.instance._get_extensions = self.instance.get_extensions
3232+
def ge(what):
3233+
return get_extensions(self.instance, what)
3234+
self.instance.get_extensions = ge
3235+
3236+
def setUp(self):
3237+
FormTestParent.setUp(self)
3238+
3239+
self.instance.registerAction('special', SpecialAction)
3240+
self.issue = self.db.issue.create (title = "hello", status='1')
3241+
self.db.commit ()
3242+
if not os.environ.has_key('SENDMAILDEBUG'):
3243+
os.environ['SENDMAILDEBUG'] = 'mail-test2.log'
3244+
self.SENDMAILDEBUG = os.environ['SENDMAILDEBUG']
3245+
page_template = """
3246+
<html>
3247+
<body>
3248+
<p tal:condition="options/error_message|nothing"
3249+
tal:repeat="m options/error_message"
3250+
tal:content="structure m"/>
3251+
<p tal:content="context/title/plain"/>
3252+
<p tal:content="context/status/plain"/>
3253+
<p tal:content="structure context/submit"/>
3254+
</body>
3255+
</html>
3256+
""".strip ()
3257+
self.form = {':action': 'special'}
3258+
cl = self.setupClient(self.form, 'issue', self.issue)
3259+
pt = RoundupPageTemplate()
3260+
pt.pt_edit(page_template, 'text/html')
3261+
self.out = []
3262+
def wh(s):
3263+
self.out.append(s)
3264+
cl.write_html = wh
3265+
def load_template(x):
3266+
return pt
3267+
cl.instance.templates.load = load_template
3268+
cl.selectTemplate = MockNull()
3269+
cl.determine_context = MockNull ()
3270+
def hasPermission(s, p, classname=None, d=None, e=None, **kw):
3271+
return True
3272+
self.hasPermission = actions.Action.hasPermission
3273+
actions.Action.hasPermission = hasPermission
3274+
self.e1 = _HTMLItem.is_edit_ok
3275+
_HTMLItem.is_edit_ok = lambda x : True
3276+
self.e2 = HTMLProperty.is_edit_ok
3277+
HTMLProperty.is_edit_ok = lambda x : True
3278+
# Make sure header check passes
3279+
cl.env['HTTP_REFERER'] = 'http://whoami.com/path/'
3280+
self.client = cl
3281+
3282+
def tearDown(self):
3283+
FormTestParent.tearDown(self)
3284+
# Remove monkey-patches
3285+
self.instance.get_extensions = self.instance._get_extensions
3286+
del self.instance._get_extensions
3287+
actions.Action.hasPermission = self.hasPermission
3288+
_HTMLItem.is_edit_ok = self.e1
3289+
HTMLProperty.is_edit_ok = self.e2
3290+
if os.path.exists(self.SENDMAILDEBUG):
3291+
#os.remove(self.SENDMAILDEBUG)
3292+
pass
3293+
3294+
def testInnerMain(self):
3295+
cl = self.client
3296+
cl.session_api = MockNull(_sid="1234567890")
3297+
self.form ['@nonce'] = anti_csrf_nonce(cl, cl)
3298+
cl.form = makeForm(self.form)
3299+
# inner_main will re-open the database!
3300+
# Note that in the template above, the rendering of the
3301+
# context/submit button will also call anti_csrf_nonce which
3302+
# does a commit of the otk to the database.
3303+
cl.inner_main()
3304+
cl.db.close()
3305+
print self.out
3306+
# Make sure the action was called
3307+
self.assertEqual(SpecialAction.x, True)
3308+
# Check that the Reject worked:
3309+
self.assertNotEqual(-1, self.out[0].index('REJECT TITLE CHANGE'))
3310+
# Re-open db
3311+
self.db.close()
3312+
self.db = self.instance.open ('admin')
3313+
# We shouldn't see any changes
3314+
self.assertEqual(self.db.issue.get(self.issue, 'title'), 'hello')
3315+
self.assertEqual(self.db.issue.get(self.issue, 'status'), '1')
3316+
31283317
# vim: set et sts=4 sw=4 :

test/test_anydbm.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from roundup.backends import get_backend
2020

2121
from db_test_base import DBTest, ROTest, SchemaTest, ClassicInitTest, config
22-
from db_test_base import HTMLItemTest
22+
from db_test_base import HTMLItemTest, SpecialActionTest
2323

2424
class anydbmOpener:
2525
module = get_backend('anydbm')
@@ -52,4 +52,8 @@ class anydbmHTMLItemTest(HTMLItemTest, unittest.TestCase):
5252
class anydbmSessionTest(anydbmOpener, DBMTest, unittest.TestCase):
5353
pass
5454

55+
class anydbmSpecialActionTestCase(anydbmOpener, SpecialActionTest,
56+
unittest.TestCase):
57+
backend = 'anydbm'
58+
5559
# vim: set filetype=python ts=4 sw=4 et si

0 commit comments

Comments
 (0)