~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_wsgi.py

  • Committer: John Arbash Meinel
  • Date: 2007-04-12 21:18:29 UTC
  • mto: This revision was merged to the branch mainline in revision 2415.
  • Revision ID: john@arbash-meinel.com-20070412211829-dduwktk8in0e6zpl
Clean up test ordering

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 chroot, memory
 
24
 
 
25
 
 
26
class TestWSGI(tests.TestCase):
 
27
 
 
28
    def setUp(self):
 
29
        tests.TestCase.setUp(self)
 
30
        self.status = None
 
31
        self.headers = None
 
32
 
 
33
    def build_environ(self, updates=None):
 
34
        """Builds an environ dict with all fields required by PEP 333.
 
35
        
 
36
        :param updates: a dict to that will be incorporated into the returned
 
37
            dict using dict.update(updates).
 
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
        }
 
57
        if updates is not None:
 
58
            environ.update(updates)
 
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
        app = wsgi.SmartWSGIApp(FakeTransport())
 
73
        self.assertIsInstance(
 
74
            app.backing_transport, chroot.ChrootTransport)
 
75
 
 
76
    def test_http_get_rejected(self):
 
77
        # GET requests are rejected.
 
78
        app = wsgi.SmartWSGIApp(FakeTransport())
 
79
        environ = self.build_environ({'REQUEST_METHOD': 'GET'})
 
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
 
87
        # WSGI environ to clone from its backing transport to get a specific
 
88
        # transport for this request.
 
89
        transport = FakeTransport()
 
90
        wsgi_app = wsgi.SmartWSGIApp(transport)
 
91
        wsgi_app.backing_transport = transport
 
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')
 
98
        environ = self.build_environ({
 
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')
 
121
        environ = self.build_environ({
 
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
       
 
143
    def test_relpath_setter_bad_path_prefix(self):
 
144
        # wsgi.RelpathSetter will reject paths with that don't match the prefix
 
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.
 
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
        
 
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
        
 
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)
 
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/')
 
183
        self.assertEqual(app.prefix, 'a prefix')
 
184
        self.assertEqual(app.path_var, 'a path_var')
 
185
 
 
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
 
 
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 = []
 
227
        self.base = 'fake:///'
 
228
 
 
229
    def abspath(self, relpath):
 
230
        return 'fake:///' + relpath
 
231
 
 
232
    def clone(self, relpath):
 
233
        self.calls.append(('clone', relpath))
 
234
        return self
 
235
 
 
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