/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
23
from bzrlib.transport import memory
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):
72
        wsgi.SmartWSGIApp(None)
73
74
    def test_http_get_rejected(self):
75
        # GET requests are rejected.
76
        app = wsgi.SmartWSGIApp(None)
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
77
        environ = self.build_environ({'REQUEST_METHOD': 'GET'})
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
78
        iterable = app(environ, self.start_response)
79
        self.read_response(iterable)
80
        self.assertEqual('405 Method not allowed', self.status)
81
        self.assertTrue(('Allow', 'POST') in self.headers)
82
        
83
    def test_smart_wsgi_app_uses_given_relpath(self):
84
        # The SmartWSGIApp should use the "bzrlib.relpath" field from the
85
        # WSGI environ to construct the transport for this request, by cloning
86
        # its base transport with the given relpath.
87
        transport = FakeTransport()
88
        wsgi_app = wsgi.SmartWSGIApp(transport)
89
        def make_request(transport, write_func):
90
            request = FakeRequest(transport, write_func)
91
            self.request = request
92
            return request
93
        wsgi_app.make_request = make_request
94
        fake_input = StringIO('fake request')
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
95
        environ = self.build_environ({
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
96
            'REQUEST_METHOD': 'POST',
97
            'CONTENT_LENGTH': len(fake_input.getvalue()),
98
            'wsgi.input': fake_input,
99
            'bzrlib.relpath': 'foo/bar',
100
        })
101
        iterable = wsgi_app(environ, self.start_response)
102
        response = self.read_response(iterable)
103
        self.assertEqual([('clone', 'foo/bar')] , transport.calls)
104
105
    def test_smart_wsgi_app_request_and_response(self):
106
        # SmartWSGIApp reads the smart request from the 'wsgi.input' file-like
107
        # object in the environ dict, and returns the response via the iterable
108
        # returned to the WSGI handler.
109
        transport = memory.MemoryTransport()
110
        transport.put_bytes('foo', 'some bytes')
111
        wsgi_app = wsgi.SmartWSGIApp(transport)
112
        def make_request(transport, write_func):
113
            request = FakeRequest(transport, write_func)
114
            self.request = request
115
            return request
116
        wsgi_app.make_request = make_request
117
        fake_input = StringIO('fake request')
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
118
        environ = self.build_environ({
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
119
            'REQUEST_METHOD': 'POST',
120
            'CONTENT_LENGTH': len(fake_input.getvalue()),
121
            'wsgi.input': fake_input,
122
            'bzrlib.relpath': 'foo',
123
        })
124
        iterable = wsgi_app(environ, self.start_response)
125
        response = self.read_response(iterable)
126
        self.assertEqual('200 OK', self.status)
127
        self.assertEqual('got bytes: fake request', response)
128
129
    def test_relpath_setter(self):
130
        # wsgi.RelpathSetter is WSGI "middleware" to set the 'bzrlib.relpath'
131
        # variable.
132
        calls = []
133
        def fake_app(environ, start_response):
134
            calls.append(environ['bzrlib.relpath'])
135
        wrapped_app = wsgi.RelpathSetter(
136
            fake_app, prefix='/abc/', path_var='FOO')
137
        wrapped_app({'FOO': '/abc/xyz/.bzr/smart'}, None)
138
        self.assertEqual(['xyz'], calls)
139
       
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
140
    def test_relpath_setter_bad_path_prefix(self):
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
141
        # wsgi.RelpathSetter will reject paths with that don't match the prefix
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
142
        # with a 404.  This is probably a sign of misconfiguration; a server
143
        # shouldn't ever be invoking our WSGI application with bad paths.
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
144
        def fake_app(environ, start_response):
145
            self.fail('The app should never be called when the path is wrong')
146
        wrapped_app = wsgi.RelpathSetter(
147
            fake_app, prefix='/abc/', path_var='FOO')
148
        iterable = wrapped_app(
149
            {'FOO': 'AAA/abc/xyz/.bzr/smart'}, self.start_response)
150
        self.read_response(iterable)
151
        self.assertTrue(self.status.startswith('404'))
152
        
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
153
    def test_relpath_setter_bad_path_suffix(self):
154
        # Similar to test_relpath_setter_bad_path_prefix: wsgi.RelpathSetter
155
        # will reject paths with that don't match the suffix '.bzr/smart' with a
156
        # 404 as well.  Again, this shouldn't be seen by our WSGI application if
157
        # the server is configured correctly.
158
        def fake_app(environ, start_response):
159
            self.fail('The app should never be called when the path is wrong')
160
        wrapped_app = wsgi.RelpathSetter(
161
            fake_app, prefix='/abc/', path_var='FOO')
162
        iterable = wrapped_app(
163
            {'FOO': '/abc/xyz/.bzr/AAA'}, self.start_response)
164
        self.read_response(iterable)
165
        self.assertTrue(self.status.startswith('404'))
166
        
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
167
    def test_make_app(self):
168
        # The make_app helper constructs a SmartWSGIApp wrapped in a
169
        # RelpathSetter.
170
        app = wsgi.make_app(
171
            root='a root',
172
            prefix='a prefix',
173
            path_var='a path_var')
174
        self.assertIsInstance(app, wsgi.RelpathSetter)
175
        self.assertIsInstance(app.app, wsgi.SmartWSGIApp)
176
        self.assertEndsWith(app.app.backing_transport.base, 'a%20root/')
177
        self.assertEqual(app.prefix, 'a prefix')
178
        self.assertEqual(app.path_var, 'a path_var')
179
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
180
    def test_incomplete_request(self):
181
        transport = FakeTransport()
182
        wsgi_app = wsgi.SmartWSGIApp(transport)
183
        def make_request(transport, write_func):
184
            request = IncompleteRequest(transport, write_func)
185
            self.request = request
186
            return request
187
        wsgi_app.make_request = make_request
188
189
        fake_input = StringIO('incomplete request')
190
        environ = self.build_environ({
191
            'REQUEST_METHOD': 'POST',
192
            'CONTENT_LENGTH': len(fake_input.getvalue()),
193
            'wsgi.input': fake_input,
194
            'bzrlib.relpath': 'foo/bar',
195
        })
196
        iterable = wsgi_app(environ, self.start_response)
197
        response = self.read_response(iterable)
198
        self.assertEqual('200 OK', self.status)
199
        self.assertEqual('error\x01incomplete request\n', response)
200
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
201
202
class FakeRequest(object):
203
    
204
    def __init__(self, transport, write_func):
205
        self.transport = transport
206
        self.write_func = write_func
207
        self.accepted_bytes = ''
208
209
    def accept_bytes(self, bytes):
210
        self.accepted_bytes = bytes
211
        self.write_func('got bytes: ' + bytes)
212
213
    def next_read_size(self):
214
        return 0
215
216
217
class FakeTransport(object):
218
219
    def __init__(self):
220
        self.calls = []
221
222
    def clone(self, relpath):
223
        self.calls.append(('clone', relpath))
224
        return self
225
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
226
227
class IncompleteRequest(FakeRequest):
228
    """A request-like object that always expects to read more bytes."""
229
230
    def next_read_size(self):
231
        # this request always asks for more
232
        return 1
233