1 # test_web.py -- Tests for the git HTTP server
2 # Copryight (C) 2010 Google, Inc.
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; version 2
7 # or (at your option) any later version of the License.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 """Tests for the Git HTTP server."""
21 from cStringIO import StringIO
23 from unittest import TestCase
25 from dulwich.objects import (
30 from dulwich.web import (
36 handle_service_request,
43 class WebTestCase(TestCase):
44 """Base TestCase that sets up some useful instance vars."""
47 self._req = HTTPGitRequest(self._environ, self._start_response)
51 def _start_response(self, status, headers):
53 self._headers = list(headers)
56 class DumbHandlersTestCase(WebTestCase):
58 def test_send_file_not_found(self):
59 list(send_file(self._req, None, 'text/plain'))
60 self.assertEquals(HTTP_NOT_FOUND, self._status)
62 def test_send_file(self):
63 f = StringIO('foobar')
64 output = ''.join(send_file(self._req, f, 'text/plain'))
65 self.assertEquals('foobar', output)
66 self.assertEquals(HTTP_OK, self._status)
67 self.assertTrue(('Content-Type', 'text/plain') in self._headers)
68 self.assertTrue(f.closed)
70 def test_send_file_buffered(self):
74 self.assertEquals([xs, xs],
75 list(send_file(self._req, f, 'text/plain')))
76 self.assertEquals(HTTP_OK, self._status)
77 self.assertTrue(('Content-Type', 'text/plain') in self._headers)
78 self.assertTrue(f.closed)
80 def test_send_file_error(self):
81 class TestFile(object):
85 def read(self, size=-1):
92 list(send_file(self._req, f, 'text/plain'))
93 self.assertEquals(HTTP_NOT_FOUND, self._status)
94 self.assertTrue(f.closed)
96 def test_get_info_refs(self):
97 self._environ['QUERY_STRING'] = ''
99 class TestTag(object):
102 def __init__(self, sha, obj_type, obj_sha):
103 self.sha = lambda: sha
104 self.object = (obj_type, obj_sha)
106 class TestBlob(object):
109 def __init__(self, sha):
110 self.sha = lambda: sha
112 blob1 = TestBlob('111')
113 blob2 = TestBlob('222')
114 blob3 = TestBlob('333')
116 tag1 = TestTag('aaa', TestTag.type, 'bbb')
117 tag2 = TestTag('bbb', TestBlob.type, '222')
119 class TestBackend(object):
121 objects = [blob1, blob2, blob3, tag1, tag2]
122 self.repo = dict((o.sha(), o) for o in objects)
127 'refs/heads/master': blob1.sha(),
128 'refs/tags/tag-tag': tag1.sha(),
129 'refs/tags/blob-tag': blob3.sha(),
132 self.assertEquals(['111\trefs/heads/master\n',
133 '333\trefs/tags/blob-tag\n',
134 'aaa\trefs/tags/tag-tag\n',
135 '222\trefs/tags/tag-tag^{}\n'],
136 list(get_info_refs(self._req, TestBackend(), None)))
139 class SmartHandlersTestCase(WebTestCase):
141 class TestProtocol(object):
142 def __init__(self, handler):
143 self._handler = handler
145 def write_pkt_line(self, line):
147 self._handler.write('flush-pkt\n')
149 self._handler.write('pkt-line: %s' % line)
151 class _TestUploadPackHandler(object):
152 def __init__(self, backend, read, write, stateless_rpc=False,
153 advertise_refs=False):
156 self.proto = SmartHandlersTestCase.TestProtocol(self)
157 self.stateless_rpc = stateless_rpc
158 self.advertise_refs = advertise_refs
161 self.write('handled input: %s' % self.read())
163 def _MakeHandler(self, *args, **kwargs):
164 self._handler = self._TestUploadPackHandler(*args, **kwargs)
168 return {'git-upload-pack': self._MakeHandler}
170 def test_handle_service_request_unknown(self):
171 mat = re.search('.*', '/git-evil-handler')
172 list(handle_service_request(self._req, 'backend', mat))
173 self.assertEquals(HTTP_FORBIDDEN, self._status)
175 def test_handle_service_request(self):
176 self._environ['wsgi.input'] = StringIO('foo')
177 mat = re.search('.*', '/git-upload-pack')
178 output = ''.join(handle_service_request(self._req, 'backend', mat,
179 services=self.services()))
180 self.assertEqual('handled input: foo', output)
181 response_type = 'application/x-git-upload-pack-response'
182 self.assertTrue(('Content-Type', response_type) in self._headers)
183 self.assertFalse(self._handler.advertise_refs)
184 self.assertTrue(self._handler.stateless_rpc)
186 def test_handle_service_request_with_length(self):
187 self._environ['wsgi.input'] = StringIO('foobar')
188 self._environ['CONTENT_LENGTH'] = 3
189 mat = re.search('.*', '/git-upload-pack')
190 output = ''.join(handle_service_request(self._req, 'backend', mat,
191 services=self.services()))
192 self.assertEqual('handled input: foo', output)
193 response_type = 'application/x-git-upload-pack-response'
194 self.assertTrue(('Content-Type', response_type) in self._headers)
196 def test_get_info_refs_unknown(self):
197 self._environ['QUERY_STRING'] = 'service=git-evil-handler'
198 list(get_info_refs(self._req, 'backend', None,
199 services=self.services()))
200 self.assertEquals(HTTP_FORBIDDEN, self._status)
202 def test_get_info_refs(self):
203 self._environ['wsgi.input'] = StringIO('foo')
204 self._environ['QUERY_STRING'] = 'service=git-upload-pack'
206 output = ''.join(get_info_refs(self._req, 'backend', None,
207 services=self.services()))
208 self.assertEquals(('pkt-line: # service=git-upload-pack\n'
210 # input is ignored by the handler
211 'handled input: '), output)
212 self.assertTrue(self._handler.advertise_refs)
213 self.assertTrue(self._handler.stateless_rpc)
216 class LengthLimitedFileTestCase(TestCase):
217 def test_no_cutoff(self):
218 f = _LengthLimitedFile(StringIO('foobar'), 1024)
219 self.assertEquals('foobar', f.read())
221 def test_cutoff(self):
222 f = _LengthLimitedFile(StringIO('foobar'), 3)
223 self.assertEquals('foo', f.read())
224 self.assertEquals('', f.read())
226 def test_multiple_reads(self):
227 f = _LengthLimitedFile(StringIO('foobar'), 3)
228 self.assertEquals('fo', f.read(2))
229 self.assertEquals('o', f.read(2))
230 self.assertEquals('', f.read())
233 class HTTPGitRequestTestCase(WebTestCase):
234 def test_not_found(self):
235 self._req.cache_forever() # cache headers should be discarded
236 message = 'Something not found'
237 self.assertEquals(message, self._req.not_found(message))
238 self.assertEquals(HTTP_NOT_FOUND, self._status)
239 self.assertEquals(set([('Content-Type', 'text/plain')]),
242 def test_forbidden(self):
243 self._req.cache_forever() # cache headers should be discarded
244 message = 'Something not found'
245 self.assertEquals(message, self._req.forbidden(message))
246 self.assertEquals(HTTP_FORBIDDEN, self._status)
247 self.assertEquals(set([('Content-Type', 'text/plain')]),
250 def test_respond_ok(self):
252 self.assertEquals([], self._headers)
253 self.assertEquals(HTTP_OK, self._status)
255 def test_respond(self):
257 self._req.respond(status=402, content_type='some/type',
258 headers=[('X-Foo', 'foo'), ('X-Bar', 'bar')])
259 self.assertEquals(set([
262 ('Content-Type', 'some/type'),
263 ('Expires', 'Fri, 01 Jan 1980 00:00:00 GMT'),
264 ('Pragma', 'no-cache'),
265 ('Cache-Control', 'no-cache, max-age=0, must-revalidate'),
266 ]), set(self._headers))
267 self.assertEquals(402, self._status)
270 class HTTPGitApplicationTestCase(TestCase):
272 self._app = HTTPGitApplication('backend')
275 def test_handler(req, backend, mat):
276 # tests interface used by all handlers
277 self.assertEquals(environ, req.environ)
278 self.assertEquals('backend', backend)
279 self.assertEquals('/foo', mat.group(0))
282 self._app.services = {
283 ('GET', re.compile('/foo$')): test_handler,
287 'REQUEST_METHOD': 'GET',
289 self.assertEquals('output', self._app(environ, None))