~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

  • Committer: Robert Collins
  • Date: 2007-07-04 08:08:13 UTC
  • mfrom: (2572 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2587.
  • Revision ID: robertc@robertcollins.net-20070704080813-wzebx0r88fvwj5rq
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
18
18
# implementation; at the moment we have urllib and pycurl.
19
19
 
20
20
# TODO: Should be renamed to bzrlib.transport.http.tests?
 
21
# TODO: What about renaming to bzrlib.tests.transport.http ?
21
22
 
 
23
from cStringIO import StringIO
 
24
import os
 
25
import select
22
26
import socket
 
27
import sys
 
28
import threading
23
29
 
24
30
import bzrlib
25
 
from bzrlib.errors import DependencyNotPresent
26
 
from bzrlib.tests import TestCase, TestSkipped
27
 
from bzrlib.transport import Transport
28
 
from bzrlib.transport.http import extract_auth, HttpTransportBase
 
31
from bzrlib import (
 
32
    errors,
 
33
    osutils,
 
34
    ui,
 
35
    urlutils,
 
36
    )
 
37
from bzrlib.tests import (
 
38
    TestCase,
 
39
    TestUIFactory,
 
40
    TestSkipped,
 
41
    StringIOWrapper,
 
42
    )
 
43
from bzrlib.tests.HttpServer import (
 
44
    HttpServer,
 
45
    HttpServer_PyCurl,
 
46
    HttpServer_urllib,
 
47
    )
 
48
from bzrlib.tests.HTTPTestUtil import (
 
49
    BadProtocolRequestHandler,
 
50
    BadStatusRequestHandler,
 
51
    ForbiddenRequestHandler,
 
52
    HTTPBasicAuthServer,
 
53
    HTTPDigestAuthServer,
 
54
    HTTPServerRedirecting,
 
55
    InvalidStatusRequestHandler,
 
56
    NoRangeRequestHandler,
 
57
    ProxyBasicAuthServer,
 
58
    ProxyDigestAuthServer,
 
59
    ProxyServer,
 
60
    SingleRangeRequestHandler,
 
61
    SingleOnlyRangeRequestHandler,
 
62
    TestCaseWithRedirectedWebserver,
 
63
    TestCaseWithTwoWebservers,
 
64
    TestCaseWithWebserver,
 
65
    WallRequestHandler,
 
66
    )
 
67
from bzrlib.transport import (
 
68
    do_catching_redirections,
 
69
    get_transport,
 
70
    Transport,
 
71
    )
 
72
from bzrlib.transport.http import (
 
73
    extract_auth,
 
74
    HttpTransportBase,
 
75
    _urllib2_wrappers,
 
76
    )
29
77
from bzrlib.transport.http._urllib import HttpTransport_urllib
30
 
from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
31
 
 
32
 
 
33
 
class FakeManager (object):
 
78
from bzrlib.transport.http._urllib2_wrappers import (
 
79
    PasswordManager,
 
80
    ProxyHandler,
 
81
    Request,
 
82
    )
 
83
 
 
84
 
 
85
class FakeManager(object):
34
86
 
35
87
    def __init__(self):
36
88
        self.credentials = []
37
 
        
 
89
 
38
90
    def add_password(self, realm, host, username, password):
39
91
        self.credentials.append([realm, host, username, password])
40
92
 
41
93
 
 
94
class RecordingServer(object):
 
95
    """A fake HTTP server.
 
96
    
 
97
    It records the bytes sent to it, and replies with a 200.
 
98
    """
 
99
 
 
100
    def __init__(self, expect_body_tail=None):
 
101
        """Constructor.
 
102
 
 
103
        :type expect_body_tail: str
 
104
        :param expect_body_tail: a reply won't be sent until this string is
 
105
            received.
 
106
        """
 
107
        self._expect_body_tail = expect_body_tail
 
108
        self.host = None
 
109
        self.port = None
 
110
        self.received_bytes = ''
 
111
 
 
112
    def setUp(self):
 
113
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
114
        self._sock.bind(('127.0.0.1', 0))
 
115
        self.host, self.port = self._sock.getsockname()
 
116
        self._ready = threading.Event()
 
117
        self._thread = threading.Thread(target=self._accept_read_and_reply)
 
118
        self._thread.setDaemon(True)
 
119
        self._thread.start()
 
120
        self._ready.wait(5)
 
121
 
 
122
    def _accept_read_and_reply(self):
 
123
        self._sock.listen(1)
 
124
        self._ready.set()
 
125
        self._sock.settimeout(5)
 
126
        try:
 
127
            conn, address = self._sock.accept()
 
128
            # On win32, the accepted connection will be non-blocking to start
 
129
            # with because we're using settimeout.
 
130
            conn.setblocking(True)
 
131
            while not self.received_bytes.endswith(self._expect_body_tail):
 
132
                self.received_bytes += conn.recv(4096)
 
133
            conn.sendall('HTTP/1.1 200 OK\r\n')
 
134
        except socket.timeout:
 
135
            # Make sure the client isn't stuck waiting for us to e.g. accept.
 
136
            self._sock.close()
 
137
        except socket.error:
 
138
            # The client may have already closed the socket.
 
139
            pass
 
140
 
 
141
    def tearDown(self):
 
142
        try:
 
143
            self._sock.close()
 
144
        except socket.error:
 
145
            # We might have already closed it.  We don't care.
 
146
            pass
 
147
        self.host = None
 
148
        self.port = None
 
149
 
 
150
 
 
151
class TestWithTransport_pycurl(object):
 
152
    """Test case to inherit from if pycurl is present"""
 
153
 
 
154
    def _get_pycurl_maybe(self):
 
155
        try:
 
156
            from bzrlib.transport.http._pycurl import PyCurlTransport
 
157
            return PyCurlTransport
 
158
        except errors.DependencyNotPresent:
 
159
            raise TestSkipped('pycurl not present')
 
160
 
 
161
    _transport = property(_get_pycurl_maybe)
 
162
 
 
163
 
42
164
class TestHttpUrls(TestCase):
43
165
 
 
166
    # TODO: This should be moved to authorization tests once they
 
167
    # are written.
 
168
 
44
169
    def test_url_parsing(self):
45
170
        f = FakeManager()
46
171
        url = extract_auth('http://example.com', f)
49
174
        url = extract_auth('http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
50
175
        self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
51
176
        self.assertEquals(1, len(f.credentials))
52
 
        self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'], f.credentials[0])
53
 
        
 
177
        self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
 
178
                          f.credentials[0])
 
179
 
 
180
 
 
181
class TestHttpTransportUrls(object):
 
182
    """Test the http urls.
 
183
 
 
184
    This MUST be used by daughter classes that also inherit from
 
185
    TestCase.
 
186
 
 
187
    We can't inherit directly from TestCase or the
 
188
    test framework will try to create an instance which cannot
 
189
    run, its implementation being incomplete.
 
190
    """
 
191
 
54
192
    def test_abs_url(self):
55
193
        """Construction of absolute http URLs"""
56
 
        t = HttpTransport_urllib('http://bazaar-vcs.org/bzr/bzr.dev/')
 
194
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
57
195
        eq = self.assertEqualDiff
58
 
        eq(t.abspath('.'),
59
 
           'http://bazaar-vcs.org/bzr/bzr.dev')
60
 
        eq(t.abspath('foo/bar'), 
61
 
           'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
62
 
        eq(t.abspath('.bzr'),
63
 
           'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
 
196
        eq(t.abspath('.'), 'http://bazaar-vcs.org/bzr/bzr.dev')
 
197
        eq(t.abspath('foo/bar'), 'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
 
198
        eq(t.abspath('.bzr'), 'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
64
199
        eq(t.abspath('.bzr/1//2/./3'),
65
200
           'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
66
201
 
67
202
    def test_invalid_http_urls(self):
68
203
        """Trap invalid construction of urls"""
69
 
        t = HttpTransport_urllib('http://bazaar-vcs.org/bzr/bzr.dev/')
70
 
        self.assertRaises(ValueError,
71
 
            t.abspath,
72
 
            '.bzr/')
 
204
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
 
205
        self.assertRaises(ValueError, t.abspath, '.bzr/')
 
206
        t = self._transport('http://http://bazaar-vcs.org/bzr/bzr.dev/')
 
207
        self.assertRaises((errors.InvalidURL, errors.ConnectionError),
 
208
                          t.has, 'foo/bar')
73
209
 
74
210
    def test_http_root_urls(self):
75
211
        """Construction of URLs from server root"""
76
 
        t = HttpTransport_urllib('http://bzr.ozlabs.org/')
 
212
        t = self._transport('http://bzr.ozlabs.org/')
77
213
        eq = self.assertEqualDiff
78
214
        eq(t.abspath('.bzr/tree-version'),
79
215
           'http://bzr.ozlabs.org/.bzr/tree-version')
80
216
 
81
217
    def test_http_impl_urls(self):
82
218
        """There are servers which ask for particular clients to connect"""
83
 
        try:
84
 
            from bzrlib.transport.http._pycurl import HttpServer_PyCurl
85
 
            server = HttpServer_PyCurl()
86
 
            try:
87
 
                server.setUp()
88
 
                url = server.get_url()
89
 
                self.assertTrue(url.startswith('http+pycurl://'))
90
 
            finally:
91
 
                server.tearDown()
92
 
        except DependencyNotPresent:
 
219
        server = self._server()
 
220
        try:
 
221
            server.setUp()
 
222
            url = server.get_url()
 
223
            self.assertTrue(url.startswith('%s://' % self._qualified_prefix))
 
224
        finally:
 
225
            server.tearDown()
 
226
 
 
227
 
 
228
class TestHttpUrls_urllib(TestHttpTransportUrls, TestCase):
 
229
    """Test http urls with urllib"""
 
230
 
 
231
    _transport = HttpTransport_urllib
 
232
    _server = HttpServer_urllib
 
233
    _qualified_prefix = 'http+urllib'
 
234
 
 
235
 
 
236
class TestHttpUrls_pycurl(TestWithTransport_pycurl, TestHttpTransportUrls,
 
237
                          TestCase):
 
238
    """Test http urls with pycurl"""
 
239
 
 
240
    _server = HttpServer_PyCurl
 
241
    _qualified_prefix = 'http+pycurl'
 
242
 
 
243
    # TODO: This should really be moved into another pycurl
 
244
    # specific test. When https tests will be implemented, take
 
245
    # this one into account.
 
246
    def test_pycurl_without_https_support(self):
 
247
        """Test that pycurl without SSL do not fail with a traceback.
 
248
 
 
249
        For the purpose of the test, we force pycurl to ignore
 
250
        https by supplying a fake version_info that do not
 
251
        support it.
 
252
        """
 
253
        try:
 
254
            import pycurl
 
255
        except ImportError:
93
256
            raise TestSkipped('pycurl not present')
94
 
 
95
 
 
96
 
class TestHttpMixins(object):
97
 
 
98
 
    def _prep_tree(self):
 
257
        # Now that we have pycurl imported, we can fake its version_info
 
258
        # This was taken from a windows pycurl without SSL
 
259
        # (thanks to bialix)
 
260
        pycurl.version_info = lambda : (2,
 
261
                                        '7.13.2',
 
262
                                        462082,
 
263
                                        'i386-pc-win32',
 
264
                                        2576,
 
265
                                        None,
 
266
                                        0,
 
267
                                        None,
 
268
                                        ('ftp', 'gopher', 'telnet',
 
269
                                         'dict', 'ldap', 'http', 'file'),
 
270
                                        None,
 
271
                                        0,
 
272
                                        None)
 
273
        self.assertRaises(errors.DependencyNotPresent, self._transport,
 
274
                          'https://launchpad.net')
 
275
 
 
276
class TestHttpConnections(object):
 
277
    """Test the http connections.
 
278
 
 
279
    This MUST be used by daughter classes that also inherit from
 
280
    TestCaseWithWebserver.
 
281
 
 
282
    We can't inherit directly from TestCaseWithWebserver or the
 
283
    test framework will try to create an instance which cannot
 
284
    run, its implementation being incomplete.
 
285
    """
 
286
 
 
287
    def setUp(self):
 
288
        TestCaseWithWebserver.setUp(self)
99
289
        self.build_tree(['xxx', 'foo/', 'foo/bar'], line_endings='binary',
100
290
                        transport=self.get_transport())
101
291
 
104
294
        t = self._transport(server.get_url())
105
295
        self.assertEqual(t.has('foo/bar'), True)
106
296
        self.assertEqual(len(server.logs), 1)
107
 
        self.assertContainsRe(server.logs[0], 
 
297
        self.assertContainsRe(server.logs[0],
108
298
            r'"HEAD /foo/bar HTTP/1.." (200|302) - "-" "bzr/')
109
299
 
110
300
    def test_http_has_not_found(self):
111
301
        server = self.get_readonly_server()
112
302
        t = self._transport(server.get_url())
113
303
        self.assertEqual(t.has('not-found'), False)
114
 
        self.assertContainsRe(server.logs[1], 
 
304
        self.assertContainsRe(server.logs[1],
115
305
            r'"HEAD /not-found HTTP/1.." 404 - "-" "bzr/')
116
306
 
117
307
    def test_http_get(self):
123
313
            'contents of foo/bar\n')
124
314
        self.assertEqual(len(server.logs), 1)
125
315
        self.assertTrue(server.logs[0].find(
126
 
            '"GET /foo/bar HTTP/1.1" 200 - "-" "bzr/%s' % bzrlib.__version__) > -1)
127
 
 
128
 
 
129
 
class TestHttpConnections_urllib(TestCaseWithWebserver, TestHttpMixins):
130
 
 
131
 
    _transport = HttpTransport_urllib
132
 
 
133
 
    def setUp(self):
134
 
        TestCaseWithWebserver.setUp(self)
135
 
        self._prep_tree()
 
316
            '"GET /foo/bar HTTP/1.1" 200 - "-" "bzr/%s'
 
317
            % bzrlib.__version__) > -1)
 
318
 
 
319
    def test_get_smart_medium(self):
 
320
        # For HTTP, get_smart_medium should return the transport object.
 
321
        server = self.get_readonly_server()
 
322
        http_transport = self._transport(server.get_url())
 
323
        medium = http_transport.get_smart_medium()
 
324
        self.assertIs(medium, http_transport)
136
325
 
137
326
    def test_has_on_bogus_host(self):
138
 
        import urllib2
139
 
        # Get a random address, so that we can be sure there is no
140
 
        # http handler there.
141
 
        s = socket.socket()
142
 
        s.bind(('localhost', 0))
143
 
        t = self._transport('http://%s:%s/' % s.getsockname())
144
 
        self.assertRaises(urllib2.URLError, t.has, 'foo/bar')
145
 
 
146
 
 
147
 
class TestHttpConnections_pycurl(TestCaseWithWebserver, TestHttpMixins):
148
 
 
149
 
    def _get_pycurl_maybe(self):
 
327
        # Get a free address and don't 'accept' on it, so that we
 
328
        # can be sure there is no http handler there, but set a
 
329
        # reasonable timeout to not slow down tests too much.
 
330
        default_timeout = socket.getdefaulttimeout()
150
331
        try:
151
 
            from bzrlib.transport.http._pycurl import PyCurlTransport
152
 
            return PyCurlTransport
153
 
        except DependencyNotPresent:
154
 
            raise TestSkipped('pycurl not present')
155
 
 
156
 
    _transport = property(_get_pycurl_maybe)
157
 
 
158
 
    def setUp(self):
159
 
        TestCaseWithWebserver.setUp(self)
160
 
        self._prep_tree()
161
 
 
 
332
            socket.setdefaulttimeout(2)
 
333
            s = socket.socket()
 
334
            s.bind(('localhost', 0))
 
335
            t = self._transport('http://%s:%s/' % s.getsockname())
 
336
            self.assertRaises(errors.ConnectionError, t.has, 'foo/bar')
 
337
        finally:
 
338
            socket.setdefaulttimeout(default_timeout)
 
339
 
 
340
 
 
341
class TestHttpConnections_urllib(TestHttpConnections, TestCaseWithWebserver):
 
342
    """Test http connections with urllib"""
 
343
 
 
344
    _transport = HttpTransport_urllib
 
345
 
 
346
 
 
347
 
 
348
class TestHttpConnections_pycurl(TestWithTransport_pycurl,
 
349
                                 TestHttpConnections,
 
350
                                 TestCaseWithWebserver):
 
351
    """Test http connections with pycurl"""
162
352
 
163
353
 
164
354
class TestHttpTransportRegistration(TestCase):
165
355
    """Test registrations of various http implementations"""
166
356
 
167
357
    def test_http_registered(self):
168
 
        import bzrlib.transport.http._urllib
169
 
        from bzrlib.transport import get_transport
170
358
        # urlllib should always be present
171
359
        t = get_transport('http+urllib://bzr.google.com/')
172
360
        self.assertIsInstance(t, Transport)
173
 
        self.assertIsInstance(t, bzrlib.transport.http._urllib.HttpTransport_urllib)
 
361
        self.assertIsInstance(t, HttpTransport_urllib)
174
362
 
175
363
 
176
364
class TestOffsets(TestCase):
197
385
        self.assertEqual([[10, 12], [22, 26]], ranges)
198
386
 
199
387
 
 
388
class TestPost(object):
 
389
 
 
390
    def _test_post_body_is_received(self, scheme):
 
391
        server = RecordingServer(expect_body_tail='end-of-body')
 
392
        server.setUp()
 
393
        self.addCleanup(server.tearDown)
 
394
        url = '%s://%s:%s/' % (scheme, server.host, server.port)
 
395
        try:
 
396
            http_transport = get_transport(url)
 
397
        except errors.UnsupportedProtocol:
 
398
            raise TestSkipped('%s not available' % scheme)
 
399
        code, response = http_transport._post('abc def end-of-body')
 
400
        self.assertTrue(
 
401
            server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
 
402
        self.assertTrue('content-length: 19\r' in server.received_bytes.lower())
 
403
        # The transport should not be assuming that the server can accept
 
404
        # chunked encoding the first time it connects, because HTTP/1.1, so we
 
405
        # check for the literal string.
 
406
        self.assertTrue(
 
407
            server.received_bytes.endswith('\r\n\r\nabc def end-of-body'))
 
408
 
 
409
 
 
410
class TestPost_urllib(TestCase, TestPost):
 
411
    """TestPost for urllib implementation"""
 
412
 
 
413
    _transport = HttpTransport_urllib
 
414
 
 
415
    def test_post_body_is_received_urllib(self):
 
416
        self._test_post_body_is_received('http+urllib')
 
417
 
 
418
 
 
419
class TestPost_pycurl(TestWithTransport_pycurl, TestCase, TestPost):
 
420
    """TestPost for pycurl implementation"""
 
421
 
 
422
    def test_post_body_is_received_pycurl(self):
 
423
        self._test_post_body_is_received('http+pycurl')
 
424
 
 
425
 
200
426
class TestRangeHeader(TestCase):
201
427
    """Test range_header method"""
202
428
 
220
446
        self.check_header('0-9,300-5000,-50',
221
447
                          ranges=[(0,9), (300,5000)],
222
448
                          tail=50)
 
449
 
 
450
 
 
451
class TestWallServer(object):
 
452
    """Tests exceptions during the connection phase"""
 
453
 
 
454
    def create_transport_readonly_server(self):
 
455
        return HttpServer(WallRequestHandler)
 
456
 
 
457
    def test_http_has(self):
 
458
        server = self.get_readonly_server()
 
459
        t = self._transport(server.get_url())
 
460
        # Unfortunately httplib (see HTTPResponse._read_status
 
461
        # for details) make no distinction between a closed
 
462
        # socket and badly formatted status line, so we can't
 
463
        # just test for ConnectionError, we have to test
 
464
        # InvalidHttpResponse too.
 
465
        self.assertRaises((errors.ConnectionError, errors.InvalidHttpResponse),
 
466
                          t.has, 'foo/bar')
 
467
 
 
468
    def test_http_get(self):
 
469
        server = self.get_readonly_server()
 
470
        t = self._transport(server.get_url())
 
471
        self.assertRaises((errors.ConnectionError, errors.InvalidHttpResponse),
 
472
                          t.get, 'foo/bar')
 
473
 
 
474
 
 
475
class TestWallServer_urllib(TestWallServer, TestCaseWithWebserver):
 
476
    """Tests "wall" server for urllib implementation"""
 
477
 
 
478
    _transport = HttpTransport_urllib
 
479
 
 
480
 
 
481
class TestWallServer_pycurl(TestWithTransport_pycurl,
 
482
                            TestWallServer,
 
483
                            TestCaseWithWebserver):
 
484
    """Tests "wall" server for pycurl implementation"""
 
485
 
 
486
 
 
487
class TestBadStatusServer(object):
 
488
    """Tests bad status from server."""
 
489
 
 
490
    def create_transport_readonly_server(self):
 
491
        return HttpServer(BadStatusRequestHandler)
 
492
 
 
493
    def test_http_has(self):
 
494
        server = self.get_readonly_server()
 
495
        t = self._transport(server.get_url())
 
496
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
 
497
 
 
498
    def test_http_get(self):
 
499
        server = self.get_readonly_server()
 
500
        t = self._transport(server.get_url())
 
501
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
 
502
 
 
503
 
 
504
class TestBadStatusServer_urllib(TestBadStatusServer, TestCaseWithWebserver):
 
505
    """Tests bad status server for urllib implementation"""
 
506
 
 
507
    _transport = HttpTransport_urllib
 
508
 
 
509
 
 
510
class TestBadStatusServer_pycurl(TestWithTransport_pycurl,
 
511
                                 TestBadStatusServer,
 
512
                                 TestCaseWithWebserver):
 
513
    """Tests bad status server for pycurl implementation"""
 
514
 
 
515
 
 
516
class TestInvalidStatusServer(TestBadStatusServer):
 
517
    """Tests invalid status from server.
 
518
 
 
519
    Both implementations raises the same error as for a bad status.
 
520
    """
 
521
 
 
522
    def create_transport_readonly_server(self):
 
523
        return HttpServer(InvalidStatusRequestHandler)
 
524
 
 
525
 
 
526
class TestInvalidStatusServer_urllib(TestInvalidStatusServer,
 
527
                                     TestCaseWithWebserver):
 
528
    """Tests invalid status server for urllib implementation"""
 
529
 
 
530
    _transport = HttpTransport_urllib
 
531
 
 
532
 
 
533
class TestInvalidStatusServer_pycurl(TestWithTransport_pycurl,
 
534
                                     TestInvalidStatusServer,
 
535
                                     TestCaseWithWebserver):
 
536
    """Tests invalid status server for pycurl implementation"""
 
537
 
 
538
 
 
539
class TestBadProtocolServer(object):
 
540
    """Tests bad protocol from server."""
 
541
 
 
542
    def create_transport_readonly_server(self):
 
543
        return HttpServer(BadProtocolRequestHandler)
 
544
 
 
545
    def test_http_has(self):
 
546
        server = self.get_readonly_server()
 
547
        t = self._transport(server.get_url())
 
548
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
 
549
 
 
550
    def test_http_get(self):
 
551
        server = self.get_readonly_server()
 
552
        t = self._transport(server.get_url())
 
553
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
 
554
 
 
555
 
 
556
class TestBadProtocolServer_urllib(TestBadProtocolServer,
 
557
                                   TestCaseWithWebserver):
 
558
    """Tests bad protocol server for urllib implementation"""
 
559
 
 
560
    _transport = HttpTransport_urllib
 
561
 
 
562
# curl don't check the protocol version
 
563
#class TestBadProtocolServer_pycurl(TestWithTransport_pycurl,
 
564
#                                   TestBadProtocolServer,
 
565
#                                   TestCaseWithWebserver):
 
566
#    """Tests bad protocol server for pycurl implementation"""
 
567
 
 
568
 
 
569
class TestForbiddenServer(object):
 
570
    """Tests forbidden server"""
 
571
 
 
572
    def create_transport_readonly_server(self):
 
573
        return HttpServer(ForbiddenRequestHandler)
 
574
 
 
575
    def test_http_has(self):
 
576
        server = self.get_readonly_server()
 
577
        t = self._transport(server.get_url())
 
578
        self.assertRaises(errors.TransportError, t.has, 'foo/bar')
 
579
 
 
580
    def test_http_get(self):
 
581
        server = self.get_readonly_server()
 
582
        t = self._transport(server.get_url())
 
583
        self.assertRaises(errors.TransportError, t.get, 'foo/bar')
 
584
 
 
585
 
 
586
class TestForbiddenServer_urllib(TestForbiddenServer, TestCaseWithWebserver):
 
587
    """Tests forbidden server for urllib implementation"""
 
588
 
 
589
    _transport = HttpTransport_urllib
 
590
 
 
591
 
 
592
class TestForbiddenServer_pycurl(TestWithTransport_pycurl,
 
593
                                 TestForbiddenServer,
 
594
                                 TestCaseWithWebserver):
 
595
    """Tests forbidden server for pycurl implementation"""
 
596
 
 
597
 
 
598
class TestRecordingServer(TestCase):
 
599
 
 
600
    def test_create(self):
 
601
        server = RecordingServer(expect_body_tail=None)
 
602
        self.assertEqual('', server.received_bytes)
 
603
        self.assertEqual(None, server.host)
 
604
        self.assertEqual(None, server.port)
 
605
 
 
606
    def test_setUp_and_tearDown(self):
 
607
        server = RecordingServer(expect_body_tail=None)
 
608
        server.setUp()
 
609
        try:
 
610
            self.assertNotEqual(None, server.host)
 
611
            self.assertNotEqual(None, server.port)
 
612
        finally:
 
613
            server.tearDown()
 
614
        self.assertEqual(None, server.host)
 
615
        self.assertEqual(None, server.port)
 
616
 
 
617
    def test_send_receive_bytes(self):
 
618
        server = RecordingServer(expect_body_tail='c')
 
619
        server.setUp()
 
620
        self.addCleanup(server.tearDown)
 
621
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
622
        sock.connect((server.host, server.port))
 
623
        sock.sendall('abc')
 
624
        self.assertEqual('HTTP/1.1 200 OK\r\n',
 
625
                         osutils.recv_all(sock, 4096))
 
626
        self.assertEqual('abc', server.received_bytes)
 
627
 
 
628
 
 
629
class TestRangeRequestServer(object):
 
630
    """Tests readv requests against server.
 
631
 
 
632
    This MUST be used by daughter classes that also inherit from
 
633
    TestCaseWithWebserver.
 
634
 
 
635
    We can't inherit directly from TestCaseWithWebserver or the
 
636
    test framework will try to create an instance which cannot
 
637
    run, its implementation being incomplete.
 
638
    """
 
639
 
 
640
    def setUp(self):
 
641
        TestCaseWithWebserver.setUp(self)
 
642
        self.build_tree_contents([('a', '0123456789')],)
 
643
 
 
644
    def test_readv(self):
 
645
        server = self.get_readonly_server()
 
646
        t = self._transport(server.get_url())
 
647
        l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
 
648
        self.assertEqual(l[0], (0, '0'))
 
649
        self.assertEqual(l[1], (1, '1'))
 
650
        self.assertEqual(l[2], (3, '34'))
 
651
        self.assertEqual(l[3], (9, '9'))
 
652
 
 
653
    def test_readv_out_of_order(self):
 
654
        server = self.get_readonly_server()
 
655
        t = self._transport(server.get_url())
 
656
        l = list(t.readv('a', ((1, 1), (9, 1), (0, 1), (3, 2))))
 
657
        self.assertEqual(l[0], (1, '1'))
 
658
        self.assertEqual(l[1], (9, '9'))
 
659
        self.assertEqual(l[2], (0, '0'))
 
660
        self.assertEqual(l[3], (3, '34'))
 
661
 
 
662
    def test_readv_invalid_ranges(self):
 
663
        server = self.get_readonly_server()
 
664
        t = self._transport(server.get_url())
 
665
 
 
666
        # This is intentionally reading off the end of the file
 
667
        # since we are sure that it cannot get there
 
668
        self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
 
669
                              t.readv, 'a', [(1,1), (8,10)])
 
670
 
 
671
        # This is trying to seek past the end of the file, it should
 
672
        # also raise a special error
 
673
        self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
 
674
                              t.readv, 'a', [(12,2)])
 
675
 
 
676
 
 
677
class TestSingleRangeRequestServer(TestRangeRequestServer):
 
678
    """Test readv against a server which accept only single range requests"""
 
679
 
 
680
    def create_transport_readonly_server(self):
 
681
        return HttpServer(SingleRangeRequestHandler)
 
682
 
 
683
 
 
684
class TestSingleRangeRequestServer_urllib(TestSingleRangeRequestServer,
 
685
                                          TestCaseWithWebserver):
 
686
    """Tests single range requests accepting server for urllib implementation"""
 
687
 
 
688
    _transport = HttpTransport_urllib
 
689
 
 
690
 
 
691
class TestSingleRangeRequestServer_pycurl(TestWithTransport_pycurl,
 
692
                                          TestSingleRangeRequestServer,
 
693
                                          TestCaseWithWebserver):
 
694
    """Tests single range requests accepting server for pycurl implementation"""
 
695
 
 
696
 
 
697
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
 
698
    """Test readv against a server which only accept single range requests"""
 
699
 
 
700
    def create_transport_readonly_server(self):
 
701
        return HttpServer(SingleOnlyRangeRequestHandler)
 
702
 
 
703
 
 
704
class TestSingleOnlyRangeRequestServer_urllib(TestSingleOnlyRangeRequestServer,
 
705
                                              TestCaseWithWebserver):
 
706
    """Tests single range requests accepting server for urllib implementation"""
 
707
 
 
708
    _transport = HttpTransport_urllib
 
709
 
 
710
 
 
711
class TestSingleOnlyRangeRequestServer_pycurl(TestWithTransport_pycurl,
 
712
                                              TestSingleOnlyRangeRequestServer,
 
713
                                              TestCaseWithWebserver):
 
714
    """Tests single range requests accepting server for pycurl implementation"""
 
715
 
 
716
 
 
717
class TestNoRangeRequestServer(TestRangeRequestServer):
 
718
    """Test readv against a server which do not accept range requests"""
 
719
 
 
720
    def create_transport_readonly_server(self):
 
721
        return HttpServer(NoRangeRequestHandler)
 
722
 
 
723
 
 
724
class TestNoRangeRequestServer_urllib(TestNoRangeRequestServer,
 
725
                                      TestCaseWithWebserver):
 
726
    """Tests range requests refusing server for urllib implementation"""
 
727
 
 
728
    _transport = HttpTransport_urllib
 
729
 
 
730
 
 
731
class TestNoRangeRequestServer_pycurl(TestWithTransport_pycurl,
 
732
                               TestNoRangeRequestServer,
 
733
                               TestCaseWithWebserver):
 
734
    """Tests range requests refusing server for pycurl implementation"""
 
735
 
 
736
 
 
737
class TestHttpProxyWhiteBox(TestCase):
 
738
    """Whitebox test proxy http authorization.
 
739
 
 
740
    Only the urllib implementation is tested here.
 
741
    """
 
742
 
 
743
    def setUp(self):
 
744
        TestCase.setUp(self)
 
745
        self._old_env = {}
 
746
 
 
747
    def tearDown(self):
 
748
        self._restore_env()
 
749
 
 
750
    def _install_env(self, env):
 
751
        for name, value in env.iteritems():
 
752
            self._old_env[name] = osutils.set_or_unset_env(name, value)
 
753
 
 
754
    def _restore_env(self):
 
755
        for name, value in self._old_env.iteritems():
 
756
            osutils.set_or_unset_env(name, value)
 
757
 
 
758
    def _proxied_request(self):
 
759
        handler = ProxyHandler(PasswordManager())
 
760
        request = Request('GET','http://baz/buzzle')
 
761
        handler.set_proxy(request, 'http')
 
762
        return request
 
763
 
 
764
    def test_empty_user(self):
 
765
        self._install_env({'http_proxy': 'http://bar.com'})
 
766
        request = self._proxied_request()
 
767
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
 
768
 
 
769
    def test_invalid_proxy(self):
 
770
        """A proxy env variable without scheme"""
 
771
        self._install_env({'http_proxy': 'host:1234'})
 
772
        self.assertRaises(errors.InvalidURL, self._proxied_request)
 
773
 
 
774
 
 
775
class TestProxyHttpServer(object):
 
776
    """Tests proxy server.
 
777
 
 
778
    This MUST be used by daughter classes that also inherit from
 
779
    TestCaseWithTwoWebservers.
 
780
 
 
781
    We can't inherit directly from TestCaseWithTwoWebservers or
 
782
    the test framework will try to create an instance which
 
783
    cannot run, its implementation being incomplete.
 
784
 
 
785
    Be aware that we do not setup a real proxy here. Instead, we
 
786
    check that the *connection* goes through the proxy by serving
 
787
    different content (the faked proxy server append '-proxied'
 
788
    to the file names).
 
789
    """
 
790
 
 
791
    # FIXME: We don't have an https server available, so we don't
 
792
    # test https connections.
 
793
 
 
794
    # FIXME: Once the test suite is better fitted to test
 
795
    # authorization schemes, test proxy authorizations too (see
 
796
    # bug #83954).
 
797
 
 
798
    def setUp(self):
 
799
        TestCaseWithTwoWebservers.setUp(self)
 
800
        self.build_tree_contents([('foo', 'contents of foo\n'),
 
801
                                  ('foo-proxied', 'proxied contents of foo\n')])
 
802
        # Let's setup some attributes for tests
 
803
        self.server = self.get_readonly_server()
 
804
        self.proxy_address = '%s:%d' % (self.server.host, self.server.port)
 
805
        self.no_proxy_host = self.proxy_address
 
806
        # The secondary server is the proxy
 
807
        self.proxy = self.get_secondary_server()
 
808
        self.proxy_url = self.proxy.get_url()
 
809
        self._old_env = {}
 
810
 
 
811
    def create_transport_secondary_server(self):
 
812
        """Creates an http server that will serve files with
 
813
        '-proxied' appended to their names.
 
814
        """
 
815
        return ProxyServer()
 
816
 
 
817
    def _install_env(self, env):
 
818
        for name, value in env.iteritems():
 
819
            self._old_env[name] = osutils.set_or_unset_env(name, value)
 
820
 
 
821
    def _restore_env(self):
 
822
        for name, value in self._old_env.iteritems():
 
823
            osutils.set_or_unset_env(name, value)
 
824
 
 
825
    def proxied_in_env(self, env):
 
826
        self._install_env(env)
 
827
        url = self.server.get_url()
 
828
        t = self._transport(url)
 
829
        try:
 
830
            self.assertEqual(t.get('foo').read(), 'proxied contents of foo\n')
 
831
        finally:
 
832
            self._restore_env()
 
833
 
 
834
    def not_proxied_in_env(self, env):
 
835
        self._install_env(env)
 
836
        url = self.server.get_url()
 
837
        t = self._transport(url)
 
838
        try:
 
839
            self.assertEqual(t.get('foo').read(), 'contents of foo\n')
 
840
        finally:
 
841
            self._restore_env()
 
842
 
 
843
    def test_http_proxy(self):
 
844
        self.proxied_in_env({'http_proxy': self.proxy_url})
 
845
 
 
846
    def test_HTTP_PROXY(self):
 
847
        self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
 
848
 
 
849
    def test_all_proxy(self):
 
850
        self.proxied_in_env({'all_proxy': self.proxy_url})
 
851
 
 
852
    def test_ALL_PROXY(self):
 
853
        self.proxied_in_env({'ALL_PROXY': self.proxy_url})
 
854
 
 
855
    def test_http_proxy_with_no_proxy(self):
 
856
        self.not_proxied_in_env({'http_proxy': self.proxy_url,
 
857
                                 'no_proxy': self.no_proxy_host})
 
858
 
 
859
    def test_HTTP_PROXY_with_NO_PROXY(self):
 
860
        self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
 
861
                                 'NO_PROXY': self.no_proxy_host})
 
862
 
 
863
    def test_all_proxy_with_no_proxy(self):
 
864
        self.not_proxied_in_env({'all_proxy': self.proxy_url,
 
865
                                 'no_proxy': self.no_proxy_host})
 
866
 
 
867
    def test_ALL_PROXY_with_NO_PROXY(self):
 
868
        self.not_proxied_in_env({'ALL_PROXY': self.proxy_url,
 
869
                                 'NO_PROXY': self.no_proxy_host})
 
870
 
 
871
    def test_http_proxy_without_scheme(self):
 
872
        self.assertRaises(errors.InvalidURL,
 
873
                          self.proxied_in_env,
 
874
                          {'http_proxy': self.proxy_address})
 
875
 
 
876
 
 
877
class TestProxyHttpServer_urllib(TestProxyHttpServer,
 
878
                                 TestCaseWithTwoWebservers):
 
879
    """Tests proxy server for urllib implementation"""
 
880
 
 
881
    _transport = HttpTransport_urllib
 
882
 
 
883
 
 
884
class TestProxyHttpServer_pycurl(TestWithTransport_pycurl,
 
885
                                 TestProxyHttpServer,
 
886
                                 TestCaseWithTwoWebservers):
 
887
    """Tests proxy server for pycurl implementation"""
 
888
 
 
889
    def setUp(self):
 
890
        TestProxyHttpServer.setUp(self)
 
891
        # Oh my ! pycurl does not check for the port as part of
 
892
        # no_proxy :-( So we just test the host part
 
893
        self.no_proxy_host = 'localhost'
 
894
 
 
895
    def test_HTTP_PROXY(self):
 
896
        # pycurl do not check HTTP_PROXY for security reasons
 
897
        # (for use in a CGI context that we do not care
 
898
        # about. Should we ?)
 
899
        raise TestSkipped()
 
900
 
 
901
    def test_HTTP_PROXY_with_NO_PROXY(self):
 
902
        raise TestSkipped()
 
903
 
 
904
    def test_http_proxy_without_scheme(self):
 
905
        # pycurl *ignores* invalid proxy env variables. If that
 
906
        # ever change in the future, this test will fail
 
907
        # indicating that pycurl do not ignore anymore such
 
908
        # variables.
 
909
        self.not_proxied_in_env({'http_proxy': self.proxy_address})
 
910
 
 
911
 
 
912
class TestRanges(object):
 
913
    """Test the Range header in GET methods..
 
914
 
 
915
    This MUST be used by daughter classes that also inherit from
 
916
    TestCaseWithWebserver.
 
917
 
 
918
    We can't inherit directly from TestCaseWithWebserver or the
 
919
    test framework will try to create an instance which cannot
 
920
    run, its implementation being incomplete.
 
921
    """
 
922
 
 
923
    def setUp(self):
 
924
        TestCaseWithWebserver.setUp(self)
 
925
        self.build_tree_contents([('a', '0123456789')],)
 
926
        server = self.get_readonly_server()
 
927
        self.transport = self._transport(server.get_url())
 
928
 
 
929
    def _file_contents(self, relpath, ranges, tail_amount=0):
 
930
         code, data = self.transport._get(relpath, ranges)
 
931
         self.assertTrue(code in (200, 206),'_get returns: %d' % code)
 
932
         for start, end in ranges:
 
933
             data.seek(start)
 
934
             yield data.read(end - start + 1)
 
935
 
 
936
    def _file_tail(self, relpath, tail_amount):
 
937
         code, data = self.transport._get(relpath, [], tail_amount)
 
938
         self.assertTrue(code in (200, 206),'_get returns: %d' % code)
 
939
         data.seek(-tail_amount + 1, 2)
 
940
         return data.read(tail_amount)
 
941
 
 
942
    def test_range_header(self):
 
943
        # Valid ranges
 
944
        map(self.assertEqual,['0', '234'],
 
945
            list(self._file_contents('a', [(0,0), (2,4)])),)
 
946
        # Tail
 
947
        self.assertEqual('789', self._file_tail('a', 3))
 
948
        # Syntactically invalid range
 
949
        self.assertRaises(errors.InvalidRange,
 
950
                          self.transport._get, 'a', [(4, 3)])
 
951
        # Semantically invalid range
 
952
        self.assertRaises(errors.InvalidRange,
 
953
                          self.transport._get, 'a', [(42, 128)])
 
954
 
 
955
 
 
956
class TestRanges_urllib(TestRanges, TestCaseWithWebserver):
 
957
    """Test the Range header in GET methods for urllib implementation"""
 
958
 
 
959
    _transport = HttpTransport_urllib
 
960
 
 
961
 
 
962
class TestRanges_pycurl(TestWithTransport_pycurl,
 
963
                        TestRanges,
 
964
                        TestCaseWithWebserver):
 
965
    """Test the Range header in GET methods for pycurl implementation"""
 
966
 
 
967
 
 
968
class TestHTTPRedirections(object):
 
969
    """Test redirection between http servers.
 
970
 
 
971
    This MUST be used by daughter classes that also inherit from
 
972
    TestCaseWithRedirectedWebserver.
 
973
 
 
974
    We can't inherit directly from TestCaseWithTwoWebservers or the
 
975
    test framework will try to create an instance which cannot
 
976
    run, its implementation being incomplete. 
 
977
    """
 
978
 
 
979
    def create_transport_secondary_server(self):
 
980
        """Create the secondary server redirecting to the primary server"""
 
981
        new = self.get_readonly_server()
 
982
 
 
983
        redirecting = HTTPServerRedirecting()
 
984
        redirecting.redirect_to(new.host, new.port)
 
985
        return redirecting
 
986
 
 
987
    def setUp(self):
 
988
        super(TestHTTPRedirections, self).setUp()
 
989
        self.build_tree_contents([('a', '0123456789'),
 
990
                                  ('bundle',
 
991
                                  '# Bazaar revision bundle v0.9\n#\n')
 
992
                                  ],)
 
993
 
 
994
        self.old_transport = self._transport(self.old_server.get_url())
 
995
 
 
996
    def test_redirected(self):
 
997
        self.assertRaises(errors.RedirectRequested, self.old_transport.get, 'a')
 
998
        t = self._transport(self.new_server.get_url())
 
999
        self.assertEqual('0123456789', t.get('a').read())
 
1000
 
 
1001
    def test_read_redirected_bundle_from_url(self):
 
1002
        from bzrlib.bundle import read_bundle_from_url
 
1003
        url = self.old_transport.abspath('bundle')
 
1004
        bundle = read_bundle_from_url(url)
 
1005
        # If read_bundle_from_url was successful we get an empty bundle
 
1006
        self.assertEqual([], bundle.revisions)
 
1007
 
 
1008
 
 
1009
class TestHTTPRedirections_urllib(TestHTTPRedirections,
 
1010
                                  TestCaseWithRedirectedWebserver):
 
1011
    """Tests redirections for urllib implementation"""
 
1012
 
 
1013
    _transport = HttpTransport_urllib
 
1014
 
 
1015
 
 
1016
 
 
1017
class TestHTTPRedirections_pycurl(TestWithTransport_pycurl,
 
1018
                                  TestHTTPRedirections,
 
1019
                                  TestCaseWithRedirectedWebserver):
 
1020
    """Tests redirections for pycurl implementation"""
 
1021
 
 
1022
 
 
1023
class RedirectedRequest(Request):
 
1024
    """Request following redirections"""
 
1025
 
 
1026
    init_orig = Request.__init__
 
1027
 
 
1028
    def __init__(self, method, url, *args, **kwargs):
 
1029
        RedirectedRequest.init_orig(self, method, url, args, kwargs)
 
1030
        self.follow_redirections = True
 
1031
 
 
1032
 
 
1033
class TestHTTPSilentRedirections_urllib(TestCaseWithRedirectedWebserver):
 
1034
    """Test redirections provided by urllib.
 
1035
 
 
1036
    http implementations do not redirect silently anymore (they
 
1037
    do not redirect at all in fact). The mechanism is still in
 
1038
    place at the _urllib2_wrappers.Request level and these tests
 
1039
    exercise it.
 
1040
 
 
1041
    For the pycurl implementation
 
1042
    the redirection have been deleted as we may deprecate pycurl
 
1043
    and I have no place to keep a working implementation.
 
1044
    -- vila 20070212
 
1045
    """
 
1046
 
 
1047
    _transport = HttpTransport_urllib
 
1048
 
 
1049
    def setUp(self):
 
1050
        super(TestHTTPSilentRedirections_urllib, self).setUp()
 
1051
        self.setup_redirected_request()
 
1052
        self.addCleanup(self.cleanup_redirected_request)
 
1053
        self.build_tree_contents([('a','a'),
 
1054
                                  ('1/',),
 
1055
                                  ('1/a', 'redirected once'),
 
1056
                                  ('2/',),
 
1057
                                  ('2/a', 'redirected twice'),
 
1058
                                  ('3/',),
 
1059
                                  ('3/a', 'redirected thrice'),
 
1060
                                  ('4/',),
 
1061
                                  ('4/a', 'redirected 4 times'),
 
1062
                                  ('5/',),
 
1063
                                  ('5/a', 'redirected 5 times'),
 
1064
                                  ],)
 
1065
 
 
1066
        self.old_transport = self._transport(self.old_server.get_url())
 
1067
 
 
1068
    def setup_redirected_request(self):
 
1069
        self.original_class = _urllib2_wrappers.Request
 
1070
        _urllib2_wrappers.Request = RedirectedRequest
 
1071
 
 
1072
    def cleanup_redirected_request(self):
 
1073
        _urllib2_wrappers.Request = self.original_class
 
1074
 
 
1075
    def create_transport_secondary_server(self):
 
1076
        """Create the secondary server, redirections are defined in the tests"""
 
1077
        return HTTPServerRedirecting()
 
1078
 
 
1079
    def test_one_redirection(self):
 
1080
        t = self.old_transport
 
1081
 
 
1082
        req = RedirectedRequest('GET', t.abspath('a'))
 
1083
        req.follow_redirections = True
 
1084
        new_prefix = 'http://%s:%s' % (self.new_server.host,
 
1085
                                       self.new_server.port)
 
1086
        self.old_server.redirections = \
 
1087
            [('(.*)', r'%s/1\1' % (new_prefix), 301),]
 
1088
        self.assertEquals('redirected once',t._perform(req).read())
 
1089
 
 
1090
    def test_five_redirections(self):
 
1091
        t = self.old_transport
 
1092
 
 
1093
        req = RedirectedRequest('GET', t.abspath('a'))
 
1094
        req.follow_redirections = True
 
1095
        old_prefix = 'http://%s:%s' % (self.old_server.host,
 
1096
                                       self.old_server.port)
 
1097
        new_prefix = 'http://%s:%s' % (self.new_server.host,
 
1098
                                       self.new_server.port)
 
1099
        self.old_server.redirections = \
 
1100
            [('/1(.*)', r'%s/2\1' % (old_prefix), 302),
 
1101
             ('/2(.*)', r'%s/3\1' % (old_prefix), 303),
 
1102
             ('/3(.*)', r'%s/4\1' % (old_prefix), 307),
 
1103
             ('/4(.*)', r'%s/5\1' % (new_prefix), 301),
 
1104
             ('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
 
1105
             ]
 
1106
        self.assertEquals('redirected 5 times',t._perform(req).read())
 
1107
 
 
1108
 
 
1109
class TestDoCatchRedirections(TestCaseWithRedirectedWebserver):
 
1110
    """Test transport.do_catching_redirections.
 
1111
 
 
1112
    We arbitrarily choose to use urllib transports
 
1113
    """
 
1114
 
 
1115
    _transport = HttpTransport_urllib
 
1116
 
 
1117
    def setUp(self):
 
1118
        super(TestDoCatchRedirections, self).setUp()
 
1119
        self.build_tree_contents([('a', '0123456789'),],)
 
1120
 
 
1121
        self.old_transport = self._transport(self.old_server.get_url())
 
1122
 
 
1123
    def get_a(self, transport):
 
1124
        return transport.get('a')
 
1125
 
 
1126
    def test_no_redirection(self):
 
1127
        t = self._transport(self.new_server.get_url())
 
1128
 
 
1129
        # We use None for redirected so that we fail if redirected
 
1130
        self.assertEquals('0123456789',
 
1131
                          do_catching_redirections(self.get_a, t, None).read())
 
1132
 
 
1133
    def test_one_redirection(self):
 
1134
        self.redirections = 0
 
1135
 
 
1136
        def redirected(transport, exception, redirection_notice):
 
1137
            self.redirections += 1
 
1138
            dir, file = urlutils.split(exception.target)
 
1139
            return self._transport(dir)
 
1140
 
 
1141
        self.assertEquals('0123456789',
 
1142
                          do_catching_redirections(self.get_a,
 
1143
                                                   self.old_transport,
 
1144
                                                   redirected
 
1145
                                                   ).read())
 
1146
        self.assertEquals(1, self.redirections)
 
1147
 
 
1148
    def test_redirection_loop(self):
 
1149
 
 
1150
        def redirected(transport, exception, redirection_notice):
 
1151
            # By using the redirected url as a base dir for the
 
1152
            # *old* transport, we create a loop: a => a/a =>
 
1153
            # a/a/a
 
1154
            return self.old_transport.clone(exception.target)
 
1155
 
 
1156
        self.assertRaises(errors.TooManyRedirections, do_catching_redirections,
 
1157
                          self.get_a, self.old_transport, redirected)
 
1158
 
 
1159
 
 
1160
class TestAuth(object):
 
1161
    """Test some authentication scheme specified by daughter class.
 
1162
 
 
1163
    This MUST be used by daughter classes that also inherit from
 
1164
    either TestCaseWithWebserver or TestCaseWithTwoWebservers.
 
1165
    """
 
1166
 
 
1167
    def setUp(self):
 
1168
        """Set up the test environment
 
1169
 
 
1170
        Daughter classes should set up their own environment
 
1171
        (including self.server) and explicitely call this
 
1172
        method. This is needed because we want to reuse the same
 
1173
        tests for proxy and no-proxy accesses which have
 
1174
        different ways of setting self.server.
 
1175
        """
 
1176
        self.build_tree_contents([('a', 'contents of a\n'),
 
1177
                                  ('b', 'contents of b\n'),])
 
1178
        self.old_factory = ui.ui_factory
 
1179
        # The following has the unfortunate side-effect of hiding any ouput
 
1180
        # during the tests (including pdb prompts). Feel free to comment them
 
1181
        # for debugging purposes but leave them in place, there are needed to
 
1182
        # run the tests without any console
 
1183
        self.old_stdout = sys.stdout
 
1184
        sys.stdout = StringIOWrapper()
 
1185
        self.addCleanup(self.restoreUIFactory)
 
1186
 
 
1187
    def restoreUIFactory(self):
 
1188
        ui.ui_factory = self.old_factory
 
1189
        sys.stdout = self.old_stdout
 
1190
 
 
1191
    def get_user_url(self, user=None, password=None):
 
1192
        """Build an url embedding user and password"""
 
1193
        url = '%s://' % self.server._url_protocol
 
1194
        if user is not None:
 
1195
            url += user
 
1196
            if password is not None:
 
1197
                url += ':' + password
 
1198
            url += '@'
 
1199
        url += '%s:%s/' % (self.server.host, self.server.port)
 
1200
        return url
 
1201
 
 
1202
    def test_no_user(self):
 
1203
        self.server.add_user('joe', 'foo')
 
1204
        t = self.get_user_transport()
 
1205
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
 
1206
        # Only one 'Authentication Required' error should occur
 
1207
        self.assertEqual(1, self.server.auth_required_errors)
 
1208
 
 
1209
    def test_empty_pass(self):
 
1210
        self.server.add_user('joe', '')
 
1211
        t = self.get_user_transport('joe', '')
 
1212
        self.assertEqual('contents of a\n', t.get('a').read())
 
1213
        # Only one 'Authentication Required' error should occur
 
1214
        self.assertEqual(1, self.server.auth_required_errors)
 
1215
 
 
1216
    def test_user_pass(self):
 
1217
        self.server.add_user('joe', 'foo')
 
1218
        t = self.get_user_transport('joe', 'foo')
 
1219
        self.assertEqual('contents of a\n', t.get('a').read())
 
1220
        # Only one 'Authentication Required' error should occur
 
1221
        self.assertEqual(1, self.server.auth_required_errors)
 
1222
 
 
1223
    def test_unknown_user(self):
 
1224
        self.server.add_user('joe', 'foo')
 
1225
        t = self.get_user_transport('bill', 'foo')
 
1226
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
 
1227
        # Two 'Authentication Required' errors should occur (the
 
1228
        # initial 'who are you' and 'I don't know you, who are
 
1229
        # you').
 
1230
        self.assertEqual(2, self.server.auth_required_errors)
 
1231
 
 
1232
    def test_wrong_pass(self):
 
1233
        self.server.add_user('joe', 'foo')
 
1234
        t = self.get_user_transport('joe', 'bar')
 
1235
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
 
1236
        # Two 'Authentication Required' errors should occur (the
 
1237
        # initial 'who are you' and 'this is not you, who are you')
 
1238
        self.assertEqual(2, self.server.auth_required_errors)
 
1239
 
 
1240
    def test_prompt_for_password(self):
 
1241
        self.server.add_user('joe', 'foo')
 
1242
        t = self.get_user_transport('joe', None)
 
1243
        ui.ui_factory = TestUIFactory(stdin='foo\n')
 
1244
        self.assertEqual('contents of a\n',t.get('a').read())
 
1245
        # stdin should be empty
 
1246
        self.assertEqual('', ui.ui_factory.stdin.readline())
 
1247
        # And we shouldn't prompt again for a different request
 
1248
        # against the same transport.
 
1249
        self.assertEqual('contents of b\n',t.get('b').read())
 
1250
        t2 = t.clone()
 
1251
        # And neither against a clone
 
1252
        self.assertEqual('contents of b\n',t2.get('b').read())
 
1253
        # Only one 'Authentication Required' error should occur
 
1254
        self.assertEqual(1, self.server.auth_required_errors)
 
1255
 
 
1256
 
 
1257
class TestHTTPAuth(TestAuth):
 
1258
    """Test HTTP authentication schemes.
 
1259
 
 
1260
    Daughter classes MUST inherit from TestCaseWithWebserver too.
 
1261
    """
 
1262
 
 
1263
    _auth_header = 'Authorization'
 
1264
 
 
1265
    def setUp(self):
 
1266
        TestCaseWithWebserver.setUp(self)
 
1267
        self.server = self.get_readonly_server()
 
1268
        TestAuth.setUp(self)
 
1269
 
 
1270
    def get_user_transport(self, user=None, password=None):
 
1271
        return self._transport(self.get_user_url(user, password))
 
1272
 
 
1273
 
 
1274
class TestProxyAuth(TestAuth):
 
1275
    """Test proxy authentication schemes.
 
1276
 
 
1277
    Daughter classes MUST also inherit from TestCaseWithWebserver.
 
1278
    """
 
1279
    _auth_header = 'Proxy-authorization'
 
1280
 
 
1281
    def setUp(self):
 
1282
        TestCaseWithWebserver.setUp(self)
 
1283
        self.server = self.get_readonly_server()
 
1284
        self._old_env = {}
 
1285
        self.addCleanup(self._restore_env)
 
1286
        TestAuth.setUp(self)
 
1287
        # Override the contents to avoid false positives
 
1288
        self.build_tree_contents([('a', 'not proxied contents of a\n'),
 
1289
                                  ('b', 'not proxied contents of b\n'),
 
1290
                                  ('a-proxied', 'contents of a\n'),
 
1291
                                  ('b-proxied', 'contents of b\n'),
 
1292
                                  ])
 
1293
 
 
1294
    def get_user_transport(self, user=None, password=None):
 
1295
        self._install_env({'all_proxy': self.get_user_url(user, password)})
 
1296
        return self._transport(self.server.get_url())
 
1297
 
 
1298
    def _install_env(self, env):
 
1299
        for name, value in env.iteritems():
 
1300
            self._old_env[name] = osutils.set_or_unset_env(name, value)
 
1301
 
 
1302
    def _restore_env(self):
 
1303
        for name, value in self._old_env.iteritems():
 
1304
            osutils.set_or_unset_env(name, value)
 
1305
 
 
1306
 
 
1307
class TestHTTPBasicAuth(TestHTTPAuth, TestCaseWithWebserver):
 
1308
    """Test http basic authentication scheme"""
 
1309
 
 
1310
    _transport = HttpTransport_urllib
 
1311
 
 
1312
    def create_transport_readonly_server(self):
 
1313
        return HTTPBasicAuthServer()
 
1314
 
 
1315
 
 
1316
class TestHTTPProxyBasicAuth(TestProxyAuth, TestCaseWithWebserver):
 
1317
    """Test proxy basic authentication scheme"""
 
1318
 
 
1319
    _transport = HttpTransport_urllib
 
1320
 
 
1321
    def create_transport_readonly_server(self):
 
1322
        return ProxyBasicAuthServer()
 
1323
 
 
1324
 
 
1325
class TestDigestAuth(object):
 
1326
    """Digest Authentication specific tests"""
 
1327
 
 
1328
    def test_changing_nonce(self):
 
1329
        self.server.add_user('joe', 'foo')
 
1330
        t = self.get_user_transport('joe', 'foo')
 
1331
        self.assertEqual('contents of a\n', t.get('a').read())
 
1332
        self.assertEqual('contents of b\n', t.get('b').read())
 
1333
        # Only one 'Authentication Required' error should have
 
1334
        # occured so far
 
1335
        self.assertEqual(1, self.server.auth_required_errors)
 
1336
        # The server invalidates the current nonce
 
1337
        self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
 
1338
        self.assertEqual('contents of a\n', t.get('a').read())
 
1339
        # Two 'Authentication Required' errors should occur (the
 
1340
        # initial 'who are you' and a second 'who are you' with the new nonce)
 
1341
        self.assertEqual(2, self.server.auth_required_errors)
 
1342
 
 
1343
 
 
1344
class TestHTTPDigestAuth(TestHTTPAuth, TestDigestAuth, TestCaseWithWebserver):
 
1345
    """Test http digest authentication scheme"""
 
1346
 
 
1347
    _transport = HttpTransport_urllib
 
1348
 
 
1349
    def create_transport_readonly_server(self):
 
1350
        return HTTPDigestAuthServer()
 
1351
 
 
1352
 
 
1353
class TestHTTPProxyDigestAuth(TestProxyAuth, TestDigestAuth,
 
1354
                              TestCaseWithWebserver):
 
1355
    """Test proxy digest authentication scheme"""
 
1356
 
 
1357
    _transport = HttpTransport_urllib
 
1358
 
 
1359
    def create_transport_readonly_server(self):
 
1360
        return ProxyDigestAuthServer()
 
1361