1
# Copyright (C) 2006 Canonical Ltd
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.
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.
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
17
"""Tests for WSGI application"""
19
from cStringIO import StringIO
21
from bzrlib import tests
22
from bzrlib.transport.http import wsgi
23
from bzrlib.transport import memory
25
class TestWSGI(tests.TestCase):
28
tests.TestCase.setUp(self)
32
def build_environ(self, **kw):
33
"""Builds an environ dict with all fields required by PEP 333.
35
The resulting environ dict will be updated with an **kw that are passed.
38
# Required CGI variables
39
'REQUEST_METHOD': 'GET',
40
'SCRIPT_NAME': '/script/name/',
41
'PATH_INFO': 'path/info',
42
'SERVER_NAME': 'test',
43
'SERVER_PORT': '9999',
44
'SERVER_PROTOCOL': 'HTTP/1.0',
46
# Required WSGI variables
47
'wsgi.version': (1,0),
48
'wsgi.url_scheme': 'http',
49
'wsgi.input': StringIO(''),
50
'wsgi.errors': StringIO(),
51
'wsgi.multithread': False,
52
'wsgi.multiprocess': False,
53
'wsgi.run_once': True,
58
def read_response(self, iterable):
60
for string in iterable:
64
def start_response(self, status, headers):
66
self.headers = headers
68
def test_construct(self):
69
wsgi.SmartWSGIApp(None)
71
def test_http_get_rejected(self):
72
# GET requests are rejected.
73
app = wsgi.SmartWSGIApp(None)
74
environ = self.build_environ(REQUEST_METHOD='GET')
75
iterable = app(environ, self.start_response)
76
self.read_response(iterable)
77
self.assertEqual('405 Method not allowed', self.status)
78
self.assertTrue(('Allow', 'POST') in self.headers)
80
def test_smart_wsgi_app_uses_given_relpath(self):
81
# The SmartWSGIApp should use the "bzrlib.relpath" field from the
82
# WSGI environ to construct the transport for this request, by cloning
83
# its base transport with the given relpath.
84
transport = FakeTransport()
85
wsgi_app = wsgi.SmartWSGIApp(transport)
86
def make_request(transport, write_func):
87
request = FakeRequest(transport, write_func)
88
self.request = request
90
wsgi_app.make_request = make_request
91
fake_input = StringIO('fake request')
92
environ = self.build_environ()
94
'REQUEST_METHOD': 'POST',
95
'CONTENT_LENGTH': len(fake_input.getvalue()),
96
'wsgi.input': fake_input,
97
'bzrlib.relpath': 'foo/bar',
99
iterable = wsgi_app(environ, self.start_response)
100
response = self.read_response(iterable)
101
self.assertEqual([('clone', 'foo/bar')] , transport.calls)
103
def test_smart_wsgi_app_request_and_response(self):
104
# SmartWSGIApp reads the smart request from the 'wsgi.input' file-like
105
# object in the environ dict, and returns the response via the iterable
106
# returned to the WSGI handler.
107
transport = memory.MemoryTransport()
108
transport.put_bytes('foo', 'some bytes')
109
wsgi_app = wsgi.SmartWSGIApp(transport)
110
def make_request(transport, write_func):
111
request = FakeRequest(transport, write_func)
112
self.request = request
114
wsgi_app.make_request = make_request
115
fake_input = StringIO('fake request')
116
environ = self.build_environ()
118
'REQUEST_METHOD': 'POST',
119
'CONTENT_LENGTH': len(fake_input.getvalue()),
120
'wsgi.input': fake_input,
121
'bzrlib.relpath': 'foo',
123
iterable = wsgi_app(environ, self.start_response)
124
response = self.read_response(iterable)
125
self.assertEqual('200 OK', self.status)
126
self.assertEqual('got bytes: fake request', response)
128
def test_relpath_setter(self):
129
# wsgi.RelpathSetter is WSGI "middleware" to set the 'bzrlib.relpath'
132
def fake_app(environ, start_response):
133
calls.append(environ['bzrlib.relpath'])
134
wrapped_app = wsgi.RelpathSetter(
135
fake_app, prefix='/abc/', path_var='FOO')
136
wrapped_app({'FOO': '/abc/xyz/.bzr/smart'}, None)
137
self.assertEqual(['xyz'], calls)
139
def test_relpath_setter_bad_path(self):
140
# wsgi.RelpathSetter will reject paths with that don't match the prefix
141
# or suffix with a 404. This is probably a sign of misconfiguration; a
142
# server shouldn't ever be invoking our WSGI application with bad paths.
143
def fake_app(environ, start_response):
144
self.fail('The app should never be called when the path is wrong')
145
wrapped_app = wsgi.RelpathSetter(
146
fake_app, prefix='/abc/', path_var='FOO')
147
iterable = wrapped_app(
148
{'FOO': 'AAA/abc/xyz/.bzr/smart'}, self.start_response)
149
self.read_response(iterable)
150
self.assertTrue(self.status.startswith('404'))
152
def test_make_app(self):
153
# The make_app helper constructs a SmartWSGIApp wrapped in a
158
path_var='a path_var')
159
self.assertIsInstance(app, wsgi.RelpathSetter)
160
self.assertIsInstance(app.app, wsgi.SmartWSGIApp)
161
self.assertEndsWith(app.app.backing_transport.base, 'a%20root/')
162
self.assertEqual(app.prefix, 'a prefix')
163
self.assertEqual(app.path_var, 'a path_var')
166
class FakeRequest(object):
168
def __init__(self, transport, write_func):
169
self.transport = transport
170
self.write_func = write_func
171
self.accepted_bytes = ''
173
def accept_bytes(self, bytes):
174
self.accepted_bytes = bytes
175
self.write_func('got bytes: ' + bytes)
177
def next_read_size(self):
181
class FakeTransport(object):
186
def clone(self, relpath):
187
self.calls.append(('clone', relpath))