• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / Products.PythonScripts / 16248875199

17 Mar 2025 07:48AM CUT coverage: 86.279%. Remained the same
16248875199

push

github

web-flow
Update Python version support. (#68)

* Drop support for Python 3.8.

* Configuring for zope-product

* Update Python version support.

84 of 144 branches covered (58.33%)

Branch coverage included in aggregate %.

941 of 1044 relevant lines covered (90.13%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.85
/src/Products/PythonScripts/tests/testPythonScript.py
1
##############################################################################
2
#
3
# Copyright (c) 2002 Zope Foundation and Contributors.
4
#
5
# This software is subject to the provisions of the Zope Public License,
6
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
7
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10
# FOR A PARTICULAR PURPOSE
11
#
12
##############################################################################
13
import codecs
1✔
14
import contextlib
1✔
15
import io
1✔
16
import os
1✔
17
import sys
1✔
18
import traceback
1✔
19
import unittest
1✔
20
import warnings
1✔
21
from urllib.error import HTTPError
1✔
22

23
import zExceptions
1✔
24
import Zope2
1✔
25
from AccessControl.Permissions import change_proxy_roles
1✔
26
from AccessControl.SecurityManagement import newSecurityManager
1✔
27
from AccessControl.SecurityManagement import noSecurityManager
1✔
28
from OFS.Folder import Folder
1✔
29
from Testing.makerequest import makerequest
1✔
30
from Testing.testbrowser import Browser
1✔
31
from Testing.ZopeTestCase import FunctionalTestCase
1✔
32

33
from ..PythonScript import PythonScript
1✔
34

35

36
HERE = os.path.dirname(__file__)
1✔
37

38

39
@contextlib.contextmanager
1✔
40
def warning_interceptor():
1✔
41
    old_stderr = sys.stderr
1✔
42
    sys.stderr = stream = io.StringIO()
1✔
43
    try:
1✔
44
        yield stream
1✔
45
    finally:
46
        sys.stderr = old_stderr
1✔
47

48

49
# Test Classes
50

51

52
def readf(name):
1✔
53
    path = os.path.join(HERE, 'tscripts', '%s.ps' % name)
1✔
54
    with open(path) as f:
1✔
55
        return f.read()
1✔
56

57

58
class DummyFolder(Folder):
1✔
59
    """ Stitch in an implementation for getPhysicalPath """
60

61
    def getPhysicalPath(self):
1✔
62
        return ()
1✔
63

64

65
class PythonScriptTestBase(unittest.TestCase):
1✔
66

67
    def setUp(self):
1✔
68
        from AccessControl import ModuleSecurityInfo
1✔
69
        from AccessControl.SecurityInfo import _appliedModuleSecurity
1✔
70
        from AccessControl.SecurityInfo import _moduleSecurity
1✔
71
        self._ms_before = _moduleSecurity.copy()
1✔
72
        self._ams_before = _appliedModuleSecurity.copy()
1✔
73
        ModuleSecurityInfo('string').declarePublic('split')  # noqa: D001
1✔
74
        ModuleSecurityInfo('sets').declarePublic('Set')  # noqa: D001
1✔
75
        newSecurityManager(None, None)
1✔
76

77
    def tearDown(self):
1✔
78
        from AccessControl.SecurityInfo import _appliedModuleSecurity
1✔
79
        from AccessControl.SecurityInfo import _moduleSecurity
1✔
80
        noSecurityManager()
1✔
81
        _moduleSecurity.clear()
1✔
82
        _moduleSecurity.update(self._ms_before)
1✔
83
        _appliedModuleSecurity.clear()
1✔
84
        _appliedModuleSecurity.update(self._ams_before)
1✔
85

86
    def _newPS(self, txt, bind=None):
1✔
87
        ps = PythonScript('ps')
1✔
88
        ps.ZBindings_edit(bind or {})
1✔
89
        ps.write(txt)
1✔
90
        ps._makeFunction()
1✔
91
        if ps.errors:
1✔
92
            raise SyntaxError(ps.errors[0])
1✔
93
        return ps
1✔
94

95
    def _filePS(self, fname, bind=None):
1✔
96
        ps = PythonScript(fname)
1✔
97
        ps.ZBindings_edit(bind or {})
1✔
98
        ps.write(readf(fname))
1✔
99
        ps._makeFunction()
1✔
100
        if ps.errors:
1!
101
            raise SyntaxError(ps.errors[0])
×
102
        return ps
1✔
103

104

105
class TestPythonScriptNoAq(PythonScriptTestBase):
1✔
106

107
    def testEmpty(self):
1✔
108
        empty = self._newPS('')()
1✔
109
        self.assertIsNone(empty)
1✔
110

111
    def testIndented(self):
1✔
112
        # This failed to compile in Zope 2.4.0b2.
113
        res = self._newPS('if 1:\n return 2')()
1✔
114
        self.assertEqual(res, 2)
1✔
115

116
    def testReturn(self):
1✔
117
        res = self._newPS('return 1')()
1✔
118
        self.assertEqual(res, 1)
1✔
119

120
    def testReturnNone(self):
1✔
121
        res = self._newPS('return')()
1✔
122
        self.assertIsNone(res)
1✔
123

124
    def testParam1(self):
1✔
125
        res = self._newPS('##parameters=x\nreturn x')('txt')
1✔
126
        self.assertEqual(res, 'txt')
1✔
127

128
    def testParam2(self):
1✔
129
        eq = self.assertEqual
1✔
130
        one, two = self._newPS('##parameters=x,y\nreturn x,y')('one', 'two')
1✔
131
        eq(one, 'one')
1✔
132
        eq(two, 'two')
1✔
133

134
    def testParam26(self):
1✔
135
        import string
1✔
136
        params = string.ascii_letters[:26]
1✔
137
        sparams = ','.join(params)
1✔
138
        ps = self._newPS(f'##parameters={sparams}\nreturn {sparams}')
1✔
139
        res = ps(*params)
1✔
140
        self.assertEqual(res, tuple(params))
1✔
141

142
    def testArithmetic(self):
1✔
143
        res = self._newPS('return 1 * 5 + 4 / 2 - 6')()
1✔
144
        self.assertEqual(res, 1)
1✔
145

146
    def testReduce(self):
1✔
147
        res = self._newPS('return reduce(lambda x, y: x + y, [1,3,5,7])')()
1✔
148
        self.assertEqual(res, 16)
1✔
149
        res = self._newPS('return reduce(lambda x, y: x + y, [1,3,5,7], 1)')()
1✔
150
        self.assertEqual(res, 17)
1✔
151

152
    def testImport(self):
1✔
153
        res = self._newPS('import string; return "7" in string.digits')()
1✔
154
        self.assertTrue(res)
1✔
155

156
    def testWhileLoop(self):
1✔
157
        res = self._filePS('while_loop')()
1✔
158
        self.assertEqual(res, 1)
1✔
159

160
    def testForLoop(self):
1✔
161
        res = self._filePS('for_loop')()
1✔
162
        self.assertEqual(res, 10)
1✔
163

164
    def testMutateLiterals(self):
1✔
165
        eq = self.assertEqual
1✔
166
        l, d = self._filePS('mutate_literals')()
1✔
167
        eq(l, [2])
1✔
168
        eq(d, {'b': 2})
1✔
169

170
    def testTupleUnpackAssignment(self):
1✔
171
        eq = self.assertEqual
1✔
172
        d, x = self._filePS('tuple_unpack_assignment')()
1✔
173
        eq(d, {'a': 0, 'b': 1, 'c': 2})
1✔
174
        eq(x, 3)
1✔
175

176
    def testDoubleNegation(self):
1✔
177
        res = self._newPS('return not not "this"')()
1✔
178
        self.assertEqual(res, 1)
1✔
179

180
    def testTryExcept(self):
1✔
181
        eq = self.assertEqual
1✔
182
        a, b = self._filePS('try_except')()
1✔
183
        eq(a, 1)
1✔
184
        eq(b, 1)
1✔
185

186
    def testBigBoolean(self):
1✔
187
        res = self._filePS('big_boolean')()
1✔
188
        self.assertTrue(res)
1✔
189

190
    def testFibonacci(self):
1✔
191
        res = self._filePS('fibonacci')()
1✔
192
        self.assertEqual(
1✔
193
            res, [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377,
194
                  610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657,
195
                  46368, 75025, 121393, 196418, 317811, 514229, 832040,
196
                  1346269, 2178309, 3524578, 5702887, 9227465, 14930352,
197
                  24157817, 39088169, 63245986])
198

199
    def testSimplePrint(self):
1✔
200
        res = self._filePS('simple_print')()
1✔
201
        self.assertEqual(res, 'a\n')
1✔
202

203
    def testComplexPrint(self):
1✔
204
        res = self._filePS('complex_print')()
1✔
205
        self.assertEqual(res, 'double\ndouble\nx: 1\ny: 0 1 2\n\n')
1✔
206

207
    def testNSBind(self):
1✔
208
        f = self._filePS('ns_bind', bind={'name_ns': '_'})
1✔
209
        bound = f.__render_with_namespace__({'yes': 1, 'no': self.fail})
210
        self.assertEqual(bound, 1)
1✔
211

212
    def testNSBindInvalidHeader(self):
1✔
213
        self.assertRaises(SyntaxError, self._filePS, 'ns_bind_invalid')
1✔
214

215
    def testBooleanMap(self):
1✔
216
        res = self._filePS('boolean_map')()
1✔
217
        self.assertTrue(res)
1✔
218

219
    def testGetSize(self):
1✔
220
        f = self._filePS('complex_print')
1✔
221
        self.assertEqual(f.get_size(), len(f.read()))
1✔
222

223
    def testBuiltinSet(self):
1✔
224
        res = self._newPS('return len(set([1, 2, 3, 1]))')()
1✔
225
        self.assertEqual(res, 3)
1✔
226

227
    def testDateTime(self):
1✔
228
        res = self._newPS(
1✔
229
            "return DateTime('2007/12/10').strftime('%d.%m.%Y')")()
230
        self.assertEqual(res, '10.12.2007')
1✔
231

232
    def testRaiseSystemExitLaunchpad257269(self):
1✔
233
        ps = self._newPS('raise SystemExit')
1✔
234
        self.assertRaises(ValueError, ps)
1✔
235

236
    def testEncodingTestDotTestAllLaunchpad257276(self):
1✔
237
        ps = self._newPS("return 'foo'.encode('test.testall')")
1✔
238
        self.assertRaises(LookupError, ps)
1✔
239

240
    def test_manage_DAVget(self):
1✔
241
        ps = makerequest(self._filePS('complete'))
1✔
242
        self.assertEqual(ps.read(), ps.manage_DAVget())
1✔
243

244
    def test_PUT_native_string(self):
1✔
245
        container = DummyFolder('container')
1✔
246
        ps = makerequest(self._filePS('complete').__of__(container))
1✔
247
        self.assertEqual(ps.title, 'This is a title')
1✔
248
        self.assertEqual(ps.body(), 'print(foo+bar+baz)\nreturn printed\n')
1✔
249
        self.assertEqual(ps.params(), 'foo, bar, baz=1')
1✔
250
        new_body = """\
1✔
251
## Script (Python) "complete"
252
##bind container=container
253
##bind context=context
254
##bind namespace=
255
##bind script=script
256
##bind subpath=traverse_subpath
257
##parameters=oops
258
##title=New Title
259
##
260
return \xe4\xe9\xee\xf6\xfc
261
"""
262
        ps.REQUEST['BODY'] = new_body
1✔
263
        ps._filepath = 'fake'
1✔
264
        ps.PUT(ps.REQUEST, ps.REQUEST.RESPONSE)
1✔
265
        self.assertEqual(ps.title, 'New Title')
1✔
266
        self.assertEqual(ps.body(), 'return \xe4\xe9\xee\xf6\xfc\n')
1✔
267
        self.assertEqual(ps.params(), 'oops')
1✔
268

269
    def test_PUT_bytes(self):
1✔
270
        container = DummyFolder('container')
1✔
271
        ps = makerequest(self._filePS('complete').__of__(container))
1✔
272
        self.assertEqual(ps.title, 'This is a title')
1✔
273
        self.assertEqual(ps.body(), 'print(foo+bar+baz)\nreturn printed\n')
1✔
274
        self.assertEqual(ps.params(), 'foo, bar, baz=1')
1✔
275
        new_body = b"""\
1✔
276
## Script (Python) "complete"
277
##bind container=container
278
##bind context=context
279
##bind namespace=
280
##bind script=script
281
##bind subpath=traverse_subpath
282
##parameters=oops
283
##title=New Title
284
##
285
return \xc3\xa4\xc3\xa9\xc3\xae\xc3\xb6\xc3\xbc
286
"""
287
        ps.REQUEST['BODY'] = new_body
1✔
288
        ps._filepath = 'fake'
1✔
289
        ps.PUT(ps.REQUEST, ps.REQUEST.RESPONSE)
1✔
290
        self.assertEqual(ps.title, 'New Title')
1✔
291
        self.assertEqual(ps.body(), 'return \xe4\xe9\xee\xf6\xfc\n')
1✔
292
        self.assertEqual(ps.params(), 'oops')
1✔
293

294
    def test_write(self):
1✔
295
        ps = self._newPS('')
1✔
296

297
        ps.write(b'return 1')
1✔
298
        self.assertEqual(ps.body(), 'return 1\n')
1✔
299

300
        ps.write('return 1')
1✔
301
        self.assertEqual(ps.body(), 'return 1\n')
1✔
302

303
    def test_factory(self):
1✔
304
        from Products.PythonScripts.PythonScript import manage_addPythonScript
1✔
305

306
        # Only passing the id
307
        container = DummyFolder('container')
1✔
308
        manage_addPythonScript(container, 'testing')
1✔
309
        self.assertEqual(container.testing.getId(), 'testing')
1✔
310
        self.assertEqual(container.testing.title, '')
1✔
311
        self.assertIn('# Example code:', container.testing.body())
1✔
312
        self.assertEqual(container.testing.params(), '')
1✔
313

314
        # Passing id and a title
315
        container = DummyFolder('container')
1✔
316
        manage_addPythonScript(container, 'testing', title='This is a title')
1✔
317
        self.assertEqual(container.testing.getId(), 'testing')
1✔
318
        self.assertEqual(container.testing.title, 'This is a title')
1✔
319
        self.assertIn('# Example code:', container.testing.body())
1✔
320
        self.assertEqual(container.testing.params(), '')
1✔
321

322
        # Passing id, title and a request that has no file
323
        container = makerequest(DummyFolder('container'))
1✔
324
        container.REQUEST.form = {}
1✔
325
        manage_addPythonScript(container, 'testing', title='This is a title',
1✔
326
                               REQUEST=container.REQUEST)
327
        self.assertEqual(container.testing.getId(), 'testing')
1✔
328
        self.assertEqual(container.testing.title, 'This is a title')
1✔
329
        self.assertIn('# Example code:', container.testing.body())
1✔
330
        self.assertEqual(container.testing.params(), '')
1✔
331

332
        # Passing id, title and a request ith a file string
333
        container = makerequest(DummyFolder('container'))
1✔
334
        container.REQUEST.form = {'file': 'return 1'}
1✔
335
        manage_addPythonScript(container, 'testing', title='This is a title',
1✔
336
                               REQUEST=container.REQUEST)
337
        self.assertEqual(container.testing.getId(), 'testing')
1✔
338
        self.assertEqual(container.testing.title, 'This is a title')
1✔
339
        self.assertEqual(container.testing.body(), 'return 1\n')
1✔
340
        self.assertEqual(container.testing.params(), '')
1✔
341

342
        # Passing id, title and a request with a file object
343
        container = makerequest(DummyFolder('container'))
1✔
344
        container.REQUEST.form = {'file': io.BytesIO(b'return 1')}
1✔
345
        manage_addPythonScript(container, 'testing', title='This is a title',
1✔
346
                               REQUEST=container.REQUEST)
347
        self.assertEqual(container.testing.getId(), 'testing')
1✔
348
        self.assertEqual(container.testing.title, 'This is a title')
1✔
349
        self.assertEqual(container.testing.body(), 'return 1\n')
1✔
350
        self.assertEqual(container.testing.params(), '')
1✔
351

352
        # Passing id, title and a file string
353
        container = makerequest(DummyFolder('container'))
1✔
354
        manage_addPythonScript(container, 'testing', title='This is a title',
1✔
355
                               file=b'return 1')
356
        self.assertEqual(container.testing.getId(), 'testing')
1✔
357
        self.assertEqual(container.testing.title, 'This is a title')
1✔
358
        self.assertEqual(container.testing.body(), 'return 1\n')
1✔
359
        self.assertEqual(container.testing.params(), '')
1✔
360

361
        # Passing id, title and a file object
362
        container = makerequest(DummyFolder('container'))
1✔
363
        manage_addPythonScript(container, 'testing', title='This is a title',
1✔
364
                               file=io.BytesIO(b'return 1'))
365
        self.assertEqual(container.testing.getId(), 'testing')
1✔
366
        self.assertEqual(container.testing.title, 'This is a title')
1✔
367
        self.assertEqual(container.testing.body(), 'return 1\n')
1✔
368
        self.assertEqual(container.testing.params(), '')
1✔
369

370
    def testCodeIntrospection(self):
1✔
371
        script = self._newPS('##parameters=a="b"')
1✔
372

373
        self.assertEqual(script.__code__.co_argcount, 1)
1✔
374
        self.assertEqual(
1✔
375
            script.__code__.co_varnames,
376
            ('a',))
377
        self.assertEqual(script.__defaults__, ('b',))
1✔
378

379

380
class TestPythonScriptErrors(PythonScriptTestBase):
1✔
381

382
    def assertPSRaises(self, error, path=None, body=None):
1✔
383
        assert not (path and body) and (path or body)
1✔
384
        if body is None:
1✔
385
            body = readf(path)
1✔
386
        if error is SyntaxError:
1✔
387
            self.assertRaises(SyntaxError, self._newPS, body)
1✔
388
        else:
389
            ps = self._newPS(body)
1✔
390
            self.assertRaises(error, ps)
1✔
391

392
    def testSubversiveExcept(self):
1✔
393
        self.assertPSRaises(SyntaxError, path='subversive_except')
1✔
394

395
    def testBadImports(self):
1✔
396
        from zExceptions import Unauthorized
1✔
397
        self.assertPSRaises(SyntaxError, body='from string import *')
1✔
398
        self.assertPSRaises(Unauthorized, body='from datetime import datetime')
1✔
399
        self.assertPSRaises(Unauthorized, body='import mmap')
1✔
400

401
    def testAttributeAssignment(self):
1✔
402
        # It's illegal to assign to attributes of anything that
403
        # doesn't have enabling security declared.
404
        # Classes (and their instances) defined by restricted code
405
        # are an exception -- they are fully readable and writable.
406
        cases = [('import string', 'string'),
1✔
407
                 ('def f(): pass', 'f'),
408
                 ]
409
        assigns = ["%s.splat = 'spam'",
1✔
410
                   "setattr(%s, '_getattr_', lambda x, y: True)",
411
                   'del %s.splat',
412
                   ]
413

414
        for defn, name in cases:
1✔
415
            for asn in assigns:
1✔
416
                func = self._newPS(defn + '\n' + asn % name)
1✔
417
                self.assertRaises(TypeError, func)
1✔
418

419
    def testBadIdentifiers(self):
1✔
420
        """Some identifiers have to be avoided.
421

422
        Background:
423
        https://github.com/zopefoundation/Zope/issues/669
424
        """
425
        bad_identifiers = [
1✔
426
            'context', 'container', 'script', 'traverse_subpath',
427
        ]
428
        for identifier in bad_identifiers:
1✔
429
            with self.assertRaises(ValueError):
1✔
430
                PythonScript(identifier)
1✔
431

432

433
class TestPythonScriptGlobals(PythonScriptTestBase):
1✔
434

435
    def setUp(self):
1✔
436
        PythonScriptTestBase.setUp(self)
1✔
437

438
    def tearDown(self):
1✔
439
        PythonScriptTestBase.tearDown(self)
1✔
440

441
    def _exec(self, script, bound_names=None, args=None, kws=None):
1✔
442
        if args is None:
1!
443
            args = ()
1✔
444
        if kws is None:
1!
445
            kws = {}
1✔
446
        bindings = {'name_container': 'container'}
1✔
447
        f = self._filePS(script, bindings)
1✔
448
        return f._exec(bound_names, args, kws)
1✔
449

450
    def testGlobalIsDeclaration(self):
1✔
451
        bindings = {'container': 7}
1✔
452
        results = self._exec('global_is_declaration', bindings)
1✔
453
        self.assertEqual(results, 8)
1✔
454

455
    def test__name__(self):
1✔
456
        f = self._filePS('class.__name__')
1✔
457
        class_name = "'script.class.__name__.<locals>.foo'>"
1✔
458

459
        self.assertEqual(f(), (class_name, "'string'"))
1✔
460

461
    def test_filepath(self):
1✔
462
        # This test is meant to raise a deprecation warning.
463
        # It used to fail mysteriously instead.
464
        def warnMe(message):
1✔
465
            warnings.warn(message, stacklevel=2)
1✔
466

467
        try:
1✔
468
            f = self._filePS('filepath')
1✔
469
            with warning_interceptor() as stream:
1✔
470
                f._exec({'container': warnMe}, (), {})
1✔
471
                self.assertIn('UserWarning: foo', stream.getvalue())
1✔
472
        except TypeError as e:
×
473
            self.fail(e)
474

475

476
class PythonScriptInterfaceConformanceTests(unittest.TestCase):
1✔
477

478
    def test_class_conforms_to_IWriteLock(self):
1✔
479
        from OFS.interfaces import IWriteLock
1✔
480
        from zope.interface.verify import verifyClass
1✔
481
        verifyClass(IWriteLock, PythonScript)
1✔
482

483

484
class PythonScriptBrowserTests(FunctionalTestCase):
1✔
485
    """Browser testing Python Scripts"""
486

487
    def setUp(self):
1✔
488
        from Products.PythonScripts.PythonScript import manage_addPythonScript
1✔
489
        super().setUp()
1✔
490

491
        Zope2.App.zcml.load_site(force=True)
1✔
492

493
        uf = self.app.acl_users
1✔
494
        uf.userFolderAddUser('manager', 'manager_pass', ['Manager'], [])
1✔
495
        manage_addPythonScript(self.app, 'py_script')
1✔
496

497
        self.browser = Browser()
1✔
498
        pw = codecs.encode(b'manager:manager_pass', 'base64').decode()
1✔
499
        self.browser.addHeader('Authorization', f'basic {pw}')
1✔
500
        self.browser.open('http://localhost/py_script/manage_main')
1✔
501

502
    def test_ZPythonScriptHTML_upload__no_file(self):
1✔
503
        """It renders an error message if no file is uploaded."""
504
        self.browser.getControl('Upload File').click()
1✔
505
        self.assertIn('No file specified', self.browser.contents)
1✔
506

507
    def test_ZPythonScriptHTML_upload__with_file(self):
1✔
508
        file_contents = b'print("hello")'
1✔
509
        self.browser.getControl('file').add_file(
1✔
510
            file_contents, 'text/plain', 'script.py')
511
        self.browser.getControl('Upload File').click()
1✔
512

513
        assert 'Saved changes.' in self.browser.contents
1✔
514

515
    def test_PythonScript_proxyroles_manager(self):
1✔
516
        test_role = 'Test Role'
1✔
517
        self.app._addRole(test_role)
1✔
518

519
        # Test the original state
520
        self.assertFalse(self.app.py_script.manage_haveProxy(test_role))
1✔
521

522
        # Go to the "Proxy" ZMI tab, grab the Proxy Roles select box,
523
        # select the new role and submit
524
        self.browser.open('http://localhost/py_script/manage_proxyForm')
1✔
525
        roles_selector = self.browser.getControl(name='roles:list')
1✔
526
        testrole_option = roles_selector.getControl(test_role)
1✔
527
        self.assertFalse(testrole_option.selected)
1✔
528
        testrole_option.selected = True
1✔
529
        self.browser.getControl('Save Changes').click()
1✔
530

531
        # The Python Script should now have a proxy role set
532
        self.assertTrue(self.app.py_script.manage_haveProxy(test_role))
1✔
533

534
    def test_PythonScript_proxyroles_nonmanager(self):
1✔
535
        # This test checks an unusual configuration where roles other than
536
        # Manager are allowed to change proxy roles.
537
        proxy_form_url = 'http://localhost/py_script/manage_proxyForm'
1✔
538
        test_role = 'Test Role'
1✔
539
        self.app._addRole(test_role)
1✔
540
        test_role_2 = 'Unprivileged Role'
1✔
541
        self.app._addRole(test_role_2)
1✔
542
        self.app.manage_permission(change_proxy_roles, ['Manager', test_role])
1✔
543

544
        # Add some test users
545
        uf = self.app.acl_users
1✔
546
        uf.userFolderAddUser('privileged', 'priv', [test_role], [])
1✔
547
        uf.userFolderAddUser('peon', 'unpriv', [test_role_2], [])
1✔
548

549
        # Test the original state
550
        self.assertFalse(self.app.py_script.manage_haveProxy(test_role))
1✔
551
        self.assertFalse(self.app.py_script.manage_haveProxy(test_role_2))
1✔
552

553
        # Attempt as unprivileged user will fail both in the browser and
554
        # from trusted code
555
        self.browser.login('peon', 'unpriv')
1✔
556
        with self.assertRaises(HTTPError):
1✔
557
            self.browser.open(proxy_form_url)
1✔
558

559
        newSecurityManager(None, uf.getUser('peon'))
1✔
560
        with self.assertRaises(zExceptions.Forbidden):
1✔
561
            self.app.py_script.manage_proxy(roles=(test_role,))
1✔
562
        self.assertFalse(self.app.py_script.manage_haveProxy(test_role))
1✔
563

564
        # Now log in as privileged user and try to set a proxy role
565
        # the privileged user does not have. This must fail.
566
        self.browser.login('privileged', 'priv')
1✔
567
        self.browser.open(proxy_form_url)
1✔
568
        roles_selector = self.browser.getControl(name='roles:list')
1✔
569
        bad_option = roles_selector.getControl(test_role_2)
1✔
570
        self.assertFalse(bad_option.selected)
1✔
571
        bad_option.selected = True
1✔
572
        with self.assertRaises(HTTPError):
1✔
573
            self.browser.getControl('Save Changes').click()
1✔
574
        self.assertFalse(self.app.py_script.manage_haveProxy(test_role_2))
1✔
575

576
        newSecurityManager(None, uf.getUser('privileged'))
1✔
577
        with self.assertRaises(zExceptions.Forbidden):
1✔
578
            self.app.py_script.manage_proxy(roles=(test_role_2,))
1✔
579
        self.assertFalse(self.app.py_script.manage_haveProxy(test_role_2))
1✔
580

581
        # Trying again as privileged user with a proxy role the user has
582
        self.browser.open(proxy_form_url)
1✔
583
        roles_selector = self.browser.getControl(name='roles:list')
1✔
584
        testrole_option = roles_selector.getControl(test_role)
1✔
585
        self.assertFalse(testrole_option.selected)
1✔
586
        testrole_option.selected = True
1✔
587
        self.browser.getControl('Save Changes').click()
1✔
588

589
        # The Python Script should now have a proxy role set
590
        self.assertTrue(self.app.py_script.manage_haveProxy(test_role))
1✔
591

592
        # Cleanup
593
        noSecurityManager()
1✔
594

595

596
class TestTraceback(FunctionalTestCase, PythonScriptTestBase):
1✔
597

598
    def _format_exception(self):
1✔
599
        return "".join(traceback.format_exception(*sys.exc_info()))
1✔
600

601
    def test_source_code_in_traceback(self):
1✔
602
        ps = self._newPS("1 / 0")
1✔
603
        try:
1✔
604
            ps()
1✔
605
        except ZeroDivisionError:
1✔
606
            formatted_exception = self._format_exception()
1✔
607
        self.assertIn("1 / 0", formatted_exception)
1✔
608

609
        ps.write("2 / 0")
1✔
610
        try:
1✔
611
            ps()
1✔
612
        except ZeroDivisionError:
1✔
613
            formatted_exception = self._format_exception()
1✔
614
        self.assertIn("2 / 0", formatted_exception)
1✔
615

616
    def test_multiple_scripts_in_traceback(self):
1✔
617
        from Products.PythonScripts.PythonScript import manage_addPythonScript
1✔
618

619
        script1_body = "container.script2()"
1✔
620
        manage_addPythonScript(
1✔
621
            self.folder,
622
            "script1",
623
            file=script1_body,
624
        )
625
        script2_body = "1 / 0"
1✔
626
        manage_addPythonScript(
1✔
627
            self.folder,
628
            "script2",
629
            file=script2_body,
630
        )
631
        try:
1✔
632
            self.folder.script1()
1✔
633
        except ZeroDivisionError:
1✔
634
            formatted_exception = self._format_exception()
1✔
635
        self.assertIn(script1_body, formatted_exception)
1✔
636
        self.assertIn(script2_body, formatted_exception)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc