comparison 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
comparison
equal deleted inserted replaced
5309:20084e2b48c3 5310:efb34cbdba7c
14 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" 14 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, 15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
17 17
18 import unittest, os, shutil, errno, imp, sys, time, pprint, base64, os.path 18 import unittest, os, shutil, errno, imp, sys, time, pprint, base64, os.path
19 import logging 19 import logging, cgi
20 import gpgmelib 20 import gpgmelib
21 from email.parser import FeedParser 21 from email.parser import FeedParser
22 22
23 import pytest 23 import pytest
24 from roundup.hyperdb import String, Password, Link, Multilink, Date, \ 24 from roundup.hyperdb import String, Password, Link, Multilink, Date, \
25 Interval, DatabaseError, Boolean, Number, Node, Integer 25 Interval, DatabaseError, Boolean, Number, Node, Integer
26 from roundup.mailer import Mailer 26 from roundup.mailer import Mailer
27 from roundup import date, password, init, instance, configuration, \ 27 from roundup import date, password, init, instance, configuration, \
28 roundupdb, i18n, hyperdb 28 roundupdb, i18n, hyperdb
29 from roundup.cgi.templating import HTMLItem 29 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
30 35
31 from mocknull import MockNull 36 from mocknull import MockNull
32 37
33 config = configuration.CoreConfig() 38 config = configuration.CoreConfig()
34 config.DATABASE = "db" 39 config.DATABASE = "db"
2293 shutil.rmtree('_test_export') 2298 shutil.rmtree('_test_export')
2294 2299
2295 # test props from args parsing 2300 # test props from args parsing
2296 def testAdminOtherCommands(self): 2301 def testAdminOtherCommands(self):
2297 import roundup.admin 2302 import roundup.admin
2298 from roundup.exceptions import UsageError
2299 2303
2300 # use the filtering setup to create a bunch of items 2304 # use the filtering setup to create a bunch of items
2301 ae, dummy1, dummy2 = self.filteringSetup() 2305 ae, dummy1, dummy2 = self.filteringSetup()
2302 # create large field 2306 # create large field
2303 self.db.priority.create(name = 'X' * 500) 2307 self.db.priority.create(name = 'X' * 500)
3123 ae(str(issue.assignedto.username),m%'username') 3127 ae(str(issue.assignedto.username),m%'username')
3124 ae(str(issue ['assignedto'].username),m%'username') 3128 ae(str(issue ['assignedto'].username),m%'username')
3125 ae(bool(issue ['assignedto']['username']),False) 3129 ae(bool(issue ['assignedto']['username']),False)
3126 ae(bool(issue ['priority']['name']),False) 3130 ae(bool(issue ['priority']['name']),False)
3127 3131
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='chef@bork.bork.bork',
3168 realname='Bork, Chef', roles='User')
3169 self.db.user.create(username='mary', address='mary@test.test',
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
3128 # vim: set et sts=4 sw=4 : 3317 # vim: set et sts=4 sw=4 :

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