/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
1
# Copyright (C) 2006 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""Tests for WSGI application"""
18
19
from cStringIO import StringIO
20
21
from bzrlib import tests
22
from bzrlib.transport.http import wsgi
2018.4.11 by Andrew Bennetts
Use ChrootTransportDecorator so that the WSGI server won't let you access the entire filesystem.
23
from bzrlib.transport import chroot, memory
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
24
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
25
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
26
class TestWSGI(tests.TestCase):
27
28
    def setUp(self):
29
        tests.TestCase.setUp(self)
30
        self.status = None
31
        self.headers = None
32
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
33
    def build_environ(self, updates=None):
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
34
        """Builds an environ dict with all fields required by PEP 333.
35
        
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
36
        :param updates: a dict to that will be incorporated into the returned
37
            dict using dict.update(updates).
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
38
        """
39
        environ = {
40
            # Required CGI variables
41
            'REQUEST_METHOD': 'GET',
42
            'SCRIPT_NAME': '/script/name/',
43
            'PATH_INFO': 'path/info',
44
            'SERVER_NAME': 'test',
45
            'SERVER_PORT': '9999',
46
            'SERVER_PROTOCOL': 'HTTP/1.0',
47
48
            # Required WSGI variables
49
            'wsgi.version': (1,0),
50
            'wsgi.url_scheme': 'http',
51
            'wsgi.input': StringIO(''),
52
            'wsgi.errors': StringIO(),
53
            'wsgi.multithread': False,
54
            'wsgi.multiprocess': False,
55
            'wsgi.run_once': True,
56
        }
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
57
        if updates is not None:
58
            environ.update(updates)
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
59
        return environ
60
        
61
    def read_response(self, iterable):
62
        response = ''
63
        for string in iterable:
64
            response += string
65
        return response
66
67
    def start_response(self, status, headers):
68
        self.status = status
69
        self.headers = headers
70
71
    def test_construct(self):
2018.4.11 by Andrew Bennetts
Use ChrootTransportDecorator so that the WSGI server won't let you access the entire filesystem.
72
        app = wsgi.SmartWSGIApp(FakeTransport())
73
        self.assertIsInstance(
2379.2.1 by Robert Collins
Rewritten chroot transport that prevents accidental chroot escapes when
74
            app.backing_transport, chroot.ChrootTransport)
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
75
76
    def test_http_get_rejected(self):
77
        # GET requests are rejected.
2018.4.11 by Andrew Bennetts
Use ChrootTransportDecorator so that the WSGI server won't let you access the entire filesystem.
78
        app = wsgi.SmartWSGIApp(FakeTransport())
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
79
        environ = self.build_environ({'REQUEST_METHOD': 'GET'})
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
80
        iterable = app(environ, self.start_response)
81
        self.read_response(iterable)
82
        self.assertEqual('405 Method not allowed', self.status)
83
        self.assertTrue(('Allow', 'POST') in self.headers)
84
        
85
    def test_smart_wsgi_app_uses_given_relpath(self):
86
        # The SmartWSGIApp should use the "bzrlib.relpath" field from the
2379.2.1 by Robert Collins
Rewritten chroot transport that prevents accidental chroot escapes when
87
        # WSGI environ to clone from its backing transport to get a specific
88
        # transport for this request.
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
89
        transport = FakeTransport()
90
        wsgi_app = wsgi.SmartWSGIApp(transport)
2379.2.1 by Robert Collins
Rewritten chroot transport that prevents accidental chroot escapes when
91
        wsgi_app.backing_transport = transport
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
92
        def make_request(transport, write_func):
93
            request = FakeRequest(transport, write_func)
94
            self.request = request
95
            return request
96
        wsgi_app.make_request = make_request
97
        fake_input = StringIO('fake request')
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
98
        environ = self.build_environ({
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
99
            'REQUEST_METHOD': 'POST',
100
            'CONTENT_LENGTH': len(fake_input.getvalue()),
101
            'wsgi.input': fake_input,
102
            'bzrlib.relpath': 'foo/bar',
103
        })
104
        iterable = wsgi_app(environ, self.start_response)
105
        response = self.read_response(iterable)
106
        self.assertEqual([('clone', 'foo/bar')] , transport.calls)
107
108
    def test_smart_wsgi_app_request_and_response(self):
109
        # SmartWSGIApp reads the smart request from the 'wsgi.input' file-like
110
        # object in the environ dict, and returns the response via the iterable
111
        # returned to the WSGI handler.
112
        transport = memory.MemoryTransport()
113
        transport.put_bytes('foo', 'some bytes')
114
        wsgi_app = wsgi.SmartWSGIApp(transport)
115
        def make_request(transport, write_func):
116
            request = FakeRequest(transport, write_func)
117
            self.request = request
118
            return request
119
        wsgi_app.make_request = make_request
120
        fake_input = StringIO('fake request')
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
121
        environ = self.build_environ({
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
122
            'REQUEST_METHOD': 'POST',
123
            'CONTENT_LENGTH': len(fake_input.getvalue()),
124
            'wsgi.input': fake_input,
125
            'bzrlib.relpath': 'foo',
126
        })
127
        iterable = wsgi_app(environ, self.start_response)
128
        response = self.read_response(iterable)
129
        self.assertEqual('200 OK', self.status)
130
        self.assertEqual('got bytes: fake request', response)
131
132
    def test_relpath_setter(self):
133
        # wsgi.RelpathSetter is WSGI "middleware" to set the 'bzrlib.relpath'
134
        # variable.
135
        calls = []
136
        def fake_app(environ, start_response):
137
            calls.append(environ['bzrlib.relpath'])
138
        wrapped_app = wsgi.RelpathSetter(
139
            fake_app, prefix='/abc/', path_var='FOO')
140
        wrapped_app({'FOO': '/abc/xyz/.bzr/smart'}, None)
141
        self.assertEqual(['xyz'], calls)
142
       
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
143
    def test_relpath_setter_bad_path_prefix(self):
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
144
        # wsgi.RelpathSetter will reject paths with that don't match the prefix
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
145
        # with a 404.  This is probably a sign of misconfiguration; a server
146
        # shouldn't ever be invoking our WSGI application with bad paths.
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
147
        def fake_app(environ, start_response):
148
            self.fail('The app should never be called when the path is wrong')
149
        wrapped_app = wsgi.RelpathSetter(
150
            fake_app, prefix='/abc/', path_var='FOO')
151
        iterable = wrapped_app(
152
            {'FOO': 'AAA/abc/xyz/.bzr/smart'}, self.start_response)
153
        self.read_response(iterable)
154
        self.assertTrue(self.status.startswith('404'))
155
        
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
156
    def test_relpath_setter_bad_path_suffix(self):
157
        # Similar to test_relpath_setter_bad_path_prefix: wsgi.RelpathSetter
158
        # will reject paths with that don't match the suffix '.bzr/smart' with a
159
        # 404 as well.  Again, this shouldn't be seen by our WSGI application if
160
        # the server is configured correctly.
161
        def fake_app(environ, start_response):
162
            self.fail('The app should never be called when the path is wrong')
163
        wrapped_app = wsgi.RelpathSetter(
164
            fake_app, prefix='/abc/', path_var='FOO')
165
        iterable = wrapped_app(
166
            {'FOO': '/abc/xyz/.bzr/AAA'}, self.start_response)
167
        self.read_response(iterable)
168
        self.assertTrue(self.status.startswith('404'))
169
        
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
170
    def test_make_app(self):
171
        # The make_app helper constructs a SmartWSGIApp wrapped in a
172
        # RelpathSetter.
173
        app = wsgi.make_app(
174
            root='a root',
175
            prefix='a prefix',
176
            path_var='a path_var')
177
        self.assertIsInstance(app, wsgi.RelpathSetter)
178
        self.assertIsInstance(app.app, wsgi.SmartWSGIApp)
2379.2.1 by Robert Collins
Rewritten chroot transport that prevents accidental chroot escapes when
179
        self.assertStartsWith(app.app.backing_transport.base, 'chroot-')
180
        backing_transport = app.app.backing_transport
181
        chroot_backing_transport = backing_transport.server.backing_transport
182
        self.assertEndsWith(chroot_backing_transport.base, 'a%20root/')
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
183
        self.assertEqual(app.prefix, 'a prefix')
184
        self.assertEqual(app.path_var, 'a path_var')
185
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
186
    def test_incomplete_request(self):
187
        transport = FakeTransport()
188
        wsgi_app = wsgi.SmartWSGIApp(transport)
189
        def make_request(transport, write_func):
190
            request = IncompleteRequest(transport, write_func)
191
            self.request = request
192
            return request
193
        wsgi_app.make_request = make_request
194
195
        fake_input = StringIO('incomplete request')
196
        environ = self.build_environ({
197
            'REQUEST_METHOD': 'POST',
198
            'CONTENT_LENGTH': len(fake_input.getvalue()),
199
            'wsgi.input': fake_input,
200
            'bzrlib.relpath': 'foo/bar',
201
        })
202
        iterable = wsgi_app(environ, self.start_response)
203
        response = self.read_response(iterable)
204
        self.assertEqual('200 OK', self.status)
205
        self.assertEqual('error\x01incomplete request\n', response)
206
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
207
208
class FakeRequest(object):
209
    
210
    def __init__(self, transport, write_func):
211
        self.transport = transport
212
        self.write_func = write_func
213
        self.accepted_bytes = ''
214
215
    def accept_bytes(self, bytes):
216
        self.accepted_bytes = bytes
217
        self.write_func('got bytes: ' + bytes)
218
219
    def next_read_size(self):
220
        return 0
221
222
223
class FakeTransport(object):
224
225
    def __init__(self):
226
        self.calls = []
2018.4.11 by Andrew Bennetts
Use ChrootTransportDecorator so that the WSGI server won't let you access the entire filesystem.
227
        self.base = 'fake:///'
228
229
    def abspath(self, relpath):
230
        return 'fake:///' + relpath
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
231
232
    def clone(self, relpath):
233
        self.calls.append(('clone', relpath))
234
        return self
235
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
236
237
class IncompleteRequest(FakeRequest):
238
    """A request-like object that always expects to read more bytes."""
239
240
    def next_read_size(self):
241
        # this request always asks for more
242
        return 1
243