1"""Tests for the CherryPy configuration system."""
2
3import io
4import os
5import sys
6import unittest
7
8import six
9
10import cherrypy
11
12from cherrypy.test import helper
13
14
15localDir = os.path.join(os.getcwd(), os.path.dirname(__file__))
16
17
18def StringIOFromNative(x):
19    return io.StringIO(six.text_type(x))
20
21
22def setup_server():
23
24    @cherrypy.config(foo='this', bar='that')
25    class Root:
26
27        def __init__(self):
28            cherrypy.config.namespaces['db'] = self.db_namespace
29
30        def db_namespace(self, k, v):
31            if k == 'scheme':
32                self.db = v
33
34        @cherrypy.expose(alias=('global_', 'xyz'))
35        def index(self, key):
36            return cherrypy.request.config.get(key, 'None')
37
38        @cherrypy.expose
39        def repr(self, key):
40            return repr(cherrypy.request.config.get(key, None))
41
42        @cherrypy.expose
43        def dbscheme(self):
44            return self.db
45
46        @cherrypy.expose
47        @cherrypy.config(**{'request.body.attempt_charsets': ['utf-16']})
48        def plain(self, x):
49            return x
50
51        favicon_ico = cherrypy.tools.staticfile.handler(
52            filename=os.path.join(localDir, '../favicon.ico'))
53
54    @cherrypy.config(foo='this2', baz='that2')
55    class Foo:
56
57        @cherrypy.expose
58        def index(self, key):
59            return cherrypy.request.config.get(key, 'None')
60        nex = index
61
62        @cherrypy.expose
63        @cherrypy.config(**{'response.headers.X-silly': 'sillyval'})
64        def silly(self):
65            return 'Hello world'
66
67        # Test the expose and config decorators
68        @cherrypy.config(foo='this3', **{'bax': 'this4'})
69        @cherrypy.expose
70        def bar(self, key):
71            return repr(cherrypy.request.config.get(key, None))
72
73    class Another:
74
75        @cherrypy.expose
76        def index(self, key):
77            return str(cherrypy.request.config.get(key, 'None'))
78
79    def raw_namespace(key, value):
80        if key == 'input.map':
81            handler = cherrypy.request.handler
82
83            def wrapper():
84                params = cherrypy.request.params
85                for name, coercer in list(value.items()):
86                    try:
87                        params[name] = coercer(params[name])
88                    except KeyError:
89                        pass
90                return handler()
91            cherrypy.request.handler = wrapper
92        elif key == 'output':
93            handler = cherrypy.request.handler
94
95            def wrapper():
96                # 'value' is a type (like int or str).
97                return value(handler())
98            cherrypy.request.handler = wrapper
99
100    @cherrypy.config(**{'raw.output': repr})
101    class Raw:
102
103        @cherrypy.expose
104        @cherrypy.config(**{'raw.input.map': {'num': int}})
105        def incr(self, num):
106            return num + 1
107
108    if not six.PY3:
109        thing3 = "thing3: unicode('test', errors='ignore')"
110    else:
111        thing3 = ''
112
113    ioconf = StringIOFromNative("""
114[/]
115neg: -1234
116filename: os.path.join(sys.prefix, "hello.py")
117thing1: cherrypy.lib.httputil.response_codes[404]
118thing2: __import__('cherrypy.tutorial', globals(), locals(), ['']).thing2
119%s
120complex: 3+2j
121mul: 6*3
122ones: "11"
123twos: "22"
124stradd: %%(ones)s + %%(twos)s + "33"
125
126[/favicon.ico]
127tools.staticfile.filename = %r
128""" % (thing3, os.path.join(localDir, 'static/dirback.jpg')))
129
130    root = Root()
131    root.foo = Foo()
132    root.raw = Raw()
133    app = cherrypy.tree.mount(root, config=ioconf)
134    app.request_class.namespaces['raw'] = raw_namespace
135
136    cherrypy.tree.mount(Another(), '/another')
137    cherrypy.config.update({'luxuryyacht': 'throatwobblermangrove',
138                            'db.scheme': r'sqlite///memory',
139                            })
140
141
142#                             Client-side code                             #
143
144
145class ConfigTests(helper.CPWebCase):
146    setup_server = staticmethod(setup_server)
147
148    def testConfig(self):
149        tests = [
150            ('/', 'nex', 'None'),
151            ('/', 'foo', 'this'),
152            ('/', 'bar', 'that'),
153            ('/xyz', 'foo', 'this'),
154            ('/foo/', 'foo', 'this2'),
155            ('/foo/', 'bar', 'that'),
156            ('/foo/', 'bax', 'None'),
157            ('/foo/bar', 'baz', "'that2'"),
158            ('/foo/nex', 'baz', 'that2'),
159            # If 'foo' == 'this', then the mount point '/another' leaks into
160            # '/'.
161            ('/another/', 'foo', 'None'),
162        ]
163        for path, key, expected in tests:
164            self.getPage(path + '?key=' + key)
165            self.assertBody(expected)
166
167        expectedconf = {
168            # From CP defaults
169            'tools.log_headers.on': False,
170            'tools.log_tracebacks.on': True,
171            'request.show_tracebacks': True,
172            'log.screen': False,
173            'environment': 'test_suite',
174            'engine.autoreload.on': False,
175            # From global config
176            'luxuryyacht': 'throatwobblermangrove',
177            # From Root._cp_config
178            'bar': 'that',
179            # From Foo._cp_config
180            'baz': 'that2',
181            # From Foo.bar._cp_config
182            'foo': 'this3',
183            'bax': 'this4',
184        }
185        for key, expected in expectedconf.items():
186            self.getPage('/foo/bar?key=' + key)
187            self.assertBody(repr(expected))
188
189    def testUnrepr(self):
190        self.getPage('/repr?key=neg')
191        self.assertBody('-1234')
192
193        self.getPage('/repr?key=filename')
194        self.assertBody(repr(os.path.join(sys.prefix, 'hello.py')))
195
196        self.getPage('/repr?key=thing1')
197        self.assertBody(repr(cherrypy.lib.httputil.response_codes[404]))
198
199        if not getattr(cherrypy.server, 'using_apache', False):
200            # The object ID's won't match up when using Apache, since the
201            # server and client are running in different processes.
202            self.getPage('/repr?key=thing2')
203            from cherrypy.tutorial import thing2
204            self.assertBody(repr(thing2))
205
206        if not six.PY3:
207            self.getPage('/repr?key=thing3')
208            self.assertBody(repr(six.text_type('test')))
209
210        self.getPage('/repr?key=complex')
211        self.assertBody('(3+2j)')
212
213        self.getPage('/repr?key=mul')
214        self.assertBody('18')
215
216        self.getPage('/repr?key=stradd')
217        self.assertBody(repr('112233'))
218
219    def testRespNamespaces(self):
220        self.getPage('/foo/silly')
221        self.assertHeader('X-silly', 'sillyval')
222        self.assertBody('Hello world')
223
224    def testCustomNamespaces(self):
225        self.getPage('/raw/incr?num=12')
226        self.assertBody('13')
227
228        self.getPage('/dbscheme')
229        self.assertBody(r'sqlite///memory')
230
231    def testHandlerToolConfigOverride(self):
232        # Assert that config overrides tool constructor args. Above, we set
233        # the favicon in the page handler to be '../favicon.ico',
234        # but then overrode it in config to be './static/dirback.jpg'.
235        self.getPage('/favicon.ico')
236        self.assertBody(open(os.path.join(localDir, 'static/dirback.jpg'),
237                             'rb').read())
238
239    def test_request_body_namespace(self):
240        self.getPage('/plain', method='POST', headers=[
241            ('Content-Type', 'application/x-www-form-urlencoded'),
242            ('Content-Length', '13')],
243            body=b'\xff\xfex\x00=\xff\xfea\x00b\x00c\x00')
244        self.assertBody('abc')
245
246
247class VariableSubstitutionTests(unittest.TestCase):
248    setup_server = staticmethod(setup_server)
249
250    def test_config(self):
251        from textwrap import dedent
252
253        # variable substitution with [DEFAULT]
254        conf = dedent("""
255        [DEFAULT]
256        dir = "/some/dir"
257        my.dir = %(dir)s + "/sub"
258
259        [my]
260        my.dir = %(dir)s + "/my/dir"
261        my.dir2 = %(my.dir)s + '/dir2'
262
263        """)
264
265        fp = StringIOFromNative(conf)
266
267        cherrypy.config.update(fp)
268        self.assertEqual(cherrypy.config['my']['my.dir'], '/some/dir/my/dir')
269        self.assertEqual(cherrypy.config['my']
270                         ['my.dir2'], '/some/dir/my/dir/dir2')
271
272
273class CallablesInConfigTest(unittest.TestCase):
274    setup_server = staticmethod(setup_server)
275
276    def test_call_with_literal_dict(self):
277        from textwrap import dedent
278        conf = dedent("""
279        [my]
280        value = dict(**{'foo': 'bar'})
281        """)
282        fp = StringIOFromNative(conf)
283        cherrypy.config.update(fp)
284        self.assertEqual(cherrypy.config['my']['value'], {'foo': 'bar'})
285
286    def test_call_with_kwargs(self):
287        from textwrap import dedent
288        conf = dedent("""
289        [my]
290        value = dict(foo="buzz", **cherrypy._test_dict)
291        """)
292        test_dict = {
293            'foo': 'bar',
294            'bar': 'foo',
295            'fizz': 'buzz'
296        }
297        cherrypy._test_dict = test_dict
298        fp = StringIOFromNative(conf)
299        cherrypy.config.update(fp)
300        test_dict['foo'] = 'buzz'
301        self.assertEqual(cherrypy.config['my']['value']['foo'], 'buzz')
302        self.assertEqual(cherrypy.config['my']['value'], test_dict)
303        del cherrypy._test_dict
304