|
16 | 16 | # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
17 | 17 |
|
18 | 18 | import unittest, os, shutil, errno, imp, sys, time, pprint, base64, os.path |
19 | | -import logging |
| 19 | +import logging, cgi |
20 | 20 | import gpgmelib |
21 | 21 | from email.parser import FeedParser |
22 | 22 |
|
|
27 | 27 | from roundup import date, password, init, instance, configuration, \ |
28 | 28 | roundupdb, i18n, hyperdb |
29 | 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 | 36 | from mocknull import MockNull |
32 | 37 |
|
@@ -2295,7 +2300,6 @@ def stderrwrite(s): |
2295 | 2300 | # test props from args parsing |
2296 | 2301 | def testAdminOtherCommands(self): |
2297 | 2302 | import roundup.admin |
2298 | | - from roundup.exceptions import UsageError |
2299 | 2303 |
|
2300 | 2304 | # use the filtering setup to create a bunch of items |
2301 | 2305 | ae, dummy1, dummy2 = self.filteringSetup() |
@@ -3125,4 +3129,189 @@ def testHTMLItemDerefFail(self): |
3125 | 3129 | ae(bool(issue ['assignedto']['username']),False) |
3126 | 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='[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 | + |
3128 | 3317 | # vim: set et sts=4 sw=4 : |
0 commit comments