/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

  • Committer: Marius Kruger
  • Date: 2007-08-12 08:15:15 UTC
  • mfrom: (2695 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2979.
  • Revision ID: amanic@gmail.com-20070812081515-vgekipfhohcuj6rn
merge with bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
# TODO: Should be renamed to bzrlib.transport.http.tests?
21
21
# TODO: What about renaming to bzrlib.tests.transport.http ?
22
22
 
 
23
from cStringIO import StringIO
23
24
import os
24
25
import select
25
26
import socket
 
27
import sys
26
28
import threading
27
29
 
28
30
import bzrlib
29
31
from bzrlib import (
30
32
    errors,
31
33
    osutils,
 
34
    ui,
32
35
    urlutils,
33
36
    )
34
37
from bzrlib.tests import (
35
38
    TestCase,
 
39
    TestUIFactory,
36
40
    TestSkipped,
 
41
    StringIOWrapper,
37
42
    )
38
43
from bzrlib.tests.HttpServer import (
39
44
    HttpServer,
43
48
from bzrlib.tests.HTTPTestUtil import (
44
49
    BadProtocolRequestHandler,
45
50
    BadStatusRequestHandler,
46
 
    FakeProxyRequestHandler,
47
51
    ForbiddenRequestHandler,
 
52
    HTTPBasicAuthServer,
 
53
    HTTPDigestAuthServer,
48
54
    HTTPServerRedirecting,
49
55
    InvalidStatusRequestHandler,
 
56
    LimitedRangeHTTPServer,
50
57
    NoRangeRequestHandler,
 
58
    ProxyBasicAuthServer,
 
59
    ProxyDigestAuthServer,
 
60
    ProxyServer,
51
61
    SingleRangeRequestHandler,
 
62
    SingleOnlyRangeRequestHandler,
52
63
    TestCaseWithRedirectedWebserver,
53
64
    TestCaseWithTwoWebservers,
54
65
    TestCaseWithWebserver,
55
66
    WallRequestHandler,
56
67
    )
57
68
from bzrlib.transport import (
 
69
    _CoalescedOffset,
58
70
    do_catching_redirections,
59
71
    get_transport,
60
72
    Transport,
65
77
    _urllib2_wrappers,
66
78
    )
67
79
from bzrlib.transport.http._urllib import HttpTransport_urllib
 
80
from bzrlib.transport.http._urllib2_wrappers import (
 
81
    PasswordManager,
 
82
    ProxyHandler,
 
83
    Request,
 
84
    )
68
85
 
69
86
 
70
87
class FakeManager(object):
187
204
    def test_invalid_http_urls(self):
188
205
        """Trap invalid construction of urls"""
189
206
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
190
 
        self.assertRaises(ValueError, t.abspath, '.bzr/')
191
 
        t = self._transport('http://http://bazaar-vcs.org/bzr/bzr.dev/')
192
207
        self.assertRaises((errors.InvalidURL, errors.ConnectionError),
193
 
                          t.has, 'foo/bar')
 
208
                          self._transport,
 
209
                          'http://http://bazaar-vcs.org/bzr/bzr.dev/')
194
210
 
195
211
    def test_http_root_urls(self):
196
212
        """Construction of URLs from server root"""
346
362
        self.assertIsInstance(t, HttpTransport_urllib)
347
363
 
348
364
 
349
 
class TestOffsets(TestCase):
350
 
    """Test offsets_to_ranges method"""
351
 
 
352
 
    def test_offsets_to_ranges_simple(self):
353
 
        to_range = HttpTransportBase.offsets_to_ranges
354
 
        ranges = to_range([(10, 1)])
355
 
        self.assertEqual([[10, 10]], ranges)
356
 
 
357
 
        ranges = to_range([(0, 1), (1, 1)])
358
 
        self.assertEqual([[0, 1]], ranges)
359
 
 
360
 
        ranges = to_range([(1, 1), (0, 1)])
361
 
        self.assertEqual([[0, 1]], ranges)
362
 
 
363
 
    def test_offset_to_ranges_overlapped(self):
364
 
        to_range = HttpTransportBase.offsets_to_ranges
365
 
 
366
 
        ranges = to_range([(10, 1), (20, 2), (22, 5)])
367
 
        self.assertEqual([[10, 10], [20, 26]], ranges)
368
 
 
369
 
        ranges = to_range([(10, 1), (11, 2), (22, 5)])
370
 
        self.assertEqual([[10, 12], [22, 26]], ranges)
371
 
 
372
 
 
373
365
class TestPost(object):
374
366
 
375
367
    def _test_post_body_is_received(self, scheme):
412
404
    """Test range_header method"""
413
405
 
414
406
    def check_header(self, value, ranges=[], tail=0):
415
 
        range_header = HttpTransportBase.range_header
416
 
        self.assertEqual(value, range_header(ranges, tail))
 
407
        offsets = [ (start, end - start + 1) for start, end in ranges]
 
408
        coalesce = Transport._coalesce_offsets
 
409
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
 
410
        range_header = HttpTransportBase._range_header
 
411
        self.assertEqual(value, range_header(coalesced, tail))
417
412
 
418
413
    def test_range_header_single(self):
419
 
        self.check_header('0-9', ranges=[[0,9]])
420
 
        self.check_header('100-109', ranges=[[100,109]])
 
414
        self.check_header('0-9', ranges=[(0,9)])
 
415
        self.check_header('100-109', ranges=[(100,109)])
421
416
 
422
417
    def test_range_header_tail(self):
423
418
        self.check_header('-10', tail=10)
679
674
    """Tests single range requests accepting server for pycurl implementation"""
680
675
 
681
676
 
 
677
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
 
678
    """Test readv against a server which only accept single range requests"""
 
679
 
 
680
    def create_transport_readonly_server(self):
 
681
        return HttpServer(SingleOnlyRangeRequestHandler)
 
682
 
 
683
 
 
684
class TestSingleOnlyRangeRequestServer_urllib(TestSingleOnlyRangeRequestServer,
 
685
                                              TestCaseWithWebserver):
 
686
    """Tests single range requests accepting server for urllib implementation"""
 
687
 
 
688
    _transport = HttpTransport_urllib
 
689
 
 
690
 
 
691
class TestSingleOnlyRangeRequestServer_pycurl(TestWithTransport_pycurl,
 
692
                                              TestSingleOnlyRangeRequestServer,
 
693
                                              TestCaseWithWebserver):
 
694
    """Tests single range requests accepting server for pycurl implementation"""
 
695
 
 
696
 
682
697
class TestNoRangeRequestServer(TestRangeRequestServer):
683
698
    """Test readv against a server which do not accept range requests"""
684
699
 
699
714
    """Tests range requests refusing server for pycurl implementation"""
700
715
 
701
716
 
 
717
class TestLimitedRangeRequestServer(object):
 
718
    """Tests readv requests against server that errors out on too much ranges.
 
719
 
 
720
    This MUST be used by daughter classes that also inherit from
 
721
    TestCaseWithWebserver.
 
722
 
 
723
    We can't inherit directly from TestCaseWithWebserver or the
 
724
    test framework will try to create an instance which cannot
 
725
    run, its implementation being incomplete.
 
726
    """
 
727
 
 
728
    range_limit = 3
 
729
 
 
730
    def create_transport_readonly_server(self):
 
731
        # Requests with more range specifiers will error out
 
732
        return LimitedRangeHTTPServer(range_limit=self.range_limit)
 
733
 
 
734
    def get_transport(self):
 
735
        return self._transport(self.get_readonly_server().get_url())
 
736
 
 
737
    def setUp(self):
 
738
        TestCaseWithWebserver.setUp(self)
 
739
        # We need to manipulate ranges that correspond to real chunks in the
 
740
        # response, so we build a content appropriately.
 
741
        filler = ''.join(['abcdefghij' for _ in range(102)])
 
742
        content = ''.join(['%04d' % v + filler for v in range(16)])
 
743
        self.build_tree_contents([('a', content)],)
 
744
 
 
745
    def test_few_ranges(self):
 
746
        t = self.get_transport()
 
747
        l = list(t.readv('a', ((0, 4), (1024, 4), )))
 
748
        self.assertEqual(l[0], (0, '0000'))
 
749
        self.assertEqual(l[1], (1024, '0001'))
 
750
        self.assertEqual(1, self.get_readonly_server().GET_request_nb)
 
751
 
 
752
    def test_a_lot_of_ranges(self):
 
753
        t = self.get_transport()
 
754
        l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
 
755
        self.assertEqual(l[0], (0, '0000'))
 
756
        self.assertEqual(l[1], (1024, '0001'))
 
757
        self.assertEqual(l[2], (4096, '0004'))
 
758
        self.assertEqual(l[3], (8192, '0008'))
 
759
        # The server will refuse to serve the first request (too much ranges),
 
760
        # a second request will succeeds.
 
761
        self.assertEqual(2, self.get_readonly_server().GET_request_nb)
 
762
 
 
763
 
 
764
class TestLimitedRangeRequestServer_urllib(TestLimitedRangeRequestServer,
 
765
                                          TestCaseWithWebserver):
 
766
    """Tests limited range requests server for urllib implementation"""
 
767
 
 
768
    _transport = HttpTransport_urllib
 
769
 
 
770
 
 
771
class TestLimitedRangeRequestServer_pycurl(TestWithTransport_pycurl,
 
772
                                          TestLimitedRangeRequestServer,
 
773
                                          TestCaseWithWebserver):
 
774
    """Tests limited range requests server for pycurl implementation"""
 
775
 
 
776
 
 
777
 
702
778
class TestHttpProxyWhiteBox(TestCase):
703
779
    """Whitebox test proxy http authorization.
704
780
 
705
 
    These tests concern urllib implementation only.
 
781
    Only the urllib implementation is tested here.
706
782
    """
707
783
 
708
784
    def setUp(self):
712
788
    def tearDown(self):
713
789
        self._restore_env()
714
790
 
715
 
    def _set_and_capture_env_var(self, name, new_value):
716
 
        """Set an environment variable, and reset it when finished."""
717
 
        self._old_env[name] = osutils.set_or_unset_env(name, new_value)
718
 
 
719
791
    def _install_env(self, env):
720
792
        for name, value in env.iteritems():
721
 
            self._set_and_capture_env_var(name, value)
 
793
            self._old_env[name] = osutils.set_or_unset_env(name, value)
722
794
 
723
795
    def _restore_env(self):
724
796
        for name, value in self._old_env.iteritems():
725
797
            osutils.set_or_unset_env(name, value)
726
798
 
727
799
    def _proxied_request(self):
728
 
        from bzrlib.transport.http._urllib2_wrappers import (
729
 
            ProxyHandler,
730
 
            Request,
731
 
            )
732
 
 
733
 
        handler = ProxyHandler()
 
800
        handler = ProxyHandler(PasswordManager())
734
801
        request = Request('GET','http://baz/buzzle')
735
802
        handler.set_proxy(request, 'http')
736
803
        return request
740
807
        request = self._proxied_request()
741
808
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
742
809
 
743
 
    def test_empty_pass(self):
744
 
        self._install_env({'http_proxy': 'http://joe@bar.com'})
745
 
        request = self._proxied_request()
746
 
        self.assertEqual('Basic ' + 'joe:'.encode('base64').strip(),
747
 
                         request.headers['Proxy-authorization'])
748
 
    def test_user_pass(self):
749
 
        self._install_env({'http_proxy': 'http://joe:foo@bar.com'})
750
 
        request = self._proxied_request()
751
 
        self.assertEqual('Basic ' + 'joe:foo'.encode('base64').strip(),
752
 
                         request.headers['Proxy-authorization'])
753
 
 
754
810
    def test_invalid_proxy(self):
755
811
        """A proxy env variable without scheme"""
756
812
        self._install_env({'http_proxy': 'host:1234'})
786
842
                                  ('foo-proxied', 'proxied contents of foo\n')])
787
843
        # Let's setup some attributes for tests
788
844
        self.server = self.get_readonly_server()
789
 
        # FIXME: We should not rely on 'localhost' being the hostname
790
 
        self.proxy_address = 'localhost:%d' % self.server.port
 
845
        self.proxy_address = '%s:%d' % (self.server.host, self.server.port)
791
846
        self.no_proxy_host = self.proxy_address
792
847
        # The secondary server is the proxy
793
848
        self.proxy = self.get_secondary_server()
798
853
        """Creates an http server that will serve files with
799
854
        '-proxied' appended to their names.
800
855
        """
801
 
        return HttpServer(FakeProxyRequestHandler)
802
 
 
803
 
    def _set_and_capture_env_var(self, name, new_value):
804
 
        """Set an environment variable, and reset it when finished."""
805
 
        self._old_env[name] = osutils.set_or_unset_env(name, new_value)
 
856
        return ProxyServer()
806
857
 
807
858
    def _install_env(self, env):
808
859
        for name, value in env.iteritems():
809
 
            self._set_and_capture_env_var(name, value)
 
860
            self._old_env[name] = osutils.set_or_unset_env(name, value)
810
861
 
811
862
    def _restore_env(self):
812
863
        for name, value in self._old_env.iteritems():
916
967
        server = self.get_readonly_server()
917
968
        self.transport = self._transport(server.get_url())
918
969
 
919
 
    def _file_contents(self, relpath, ranges, tail_amount=0):
920
 
         code, data = self.transport._get(relpath, ranges)
921
 
         self.assertTrue(code in (200, 206),'_get returns: %d' % code)
922
 
         for start, end in ranges:
923
 
             data.seek(start)
924
 
             yield data.read(end - start + 1)
 
970
    def _file_contents(self, relpath, ranges):
 
971
        offsets = [ (start, end - start + 1) for start, end in ranges]
 
972
        coalesce = self.transport._coalesce_offsets
 
973
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
 
974
        code, data = self.transport._get(relpath, coalesced)
 
975
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
 
976
        for start, end in ranges:
 
977
            data.seek(start)
 
978
            yield data.read(end - start + 1)
925
979
 
926
980
    def _file_tail(self, relpath, tail_amount):
927
 
         code, data = self.transport._get(relpath, [], tail_amount)
928
 
         self.assertTrue(code in (200, 206),'_get returns: %d' % code)
929
 
         data.seek(-tail_amount + 1, 2)
930
 
         return data.read(tail_amount)
 
981
        code, data = self.transport._get(relpath, [], tail_amount)
 
982
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
 
983
        data.seek(-tail_amount + 1, 2)
 
984
        return data.read(tail_amount)
931
985
 
932
986
    def test_range_header(self):
933
987
        # Valid ranges
936
990
        # Tail
937
991
        self.assertEqual('789', self._file_tail('a', 3))
938
992
        # Syntactically invalid range
939
 
        self.assertRaises(errors.InvalidRange,
940
 
                          self.transport._get, 'a', [(4, 3)])
 
993
        self.assertListRaises(errors.InvalidRange,
 
994
                          self._file_contents, 'a', [(4, 3)])
941
995
        # Semantically invalid range
942
 
        self.assertRaises(errors.InvalidRange,
943
 
                          self.transport._get, 'a', [(42, 128)])
 
996
        self.assertListRaises(errors.InvalidRange,
 
997
                          self._file_contents, 'a', [(42, 128)])
944
998
 
945
999
 
946
1000
class TestRanges_urllib(TestRanges, TestCaseWithWebserver):
1010
1064
    """Tests redirections for pycurl implementation"""
1011
1065
 
1012
1066
 
1013
 
class RedirectedRequest(_urllib2_wrappers.Request):
 
1067
class RedirectedRequest(Request):
1014
1068
    """Request following redirections"""
1015
1069
 
1016
 
    init_orig = _urllib2_wrappers.Request.__init__
 
1070
    init_orig = Request.__init__
1017
1071
 
1018
1072
    def __init__(self, method, url, *args, **kwargs):
1019
1073
        RedirectedRequest.init_orig(self, method, url, args, kwargs)
1145
1199
 
1146
1200
        self.assertRaises(errors.TooManyRedirections, do_catching_redirections,
1147
1201
                          self.get_a, self.old_transport, redirected)
 
1202
 
 
1203
 
 
1204
class TestAuth(object):
 
1205
    """Test some authentication scheme specified by daughter class.
 
1206
 
 
1207
    This MUST be used by daughter classes that also inherit from
 
1208
    either TestCaseWithWebserver or TestCaseWithTwoWebservers.
 
1209
    """
 
1210
 
 
1211
    def setUp(self):
 
1212
        """Set up the test environment
 
1213
 
 
1214
        Daughter classes should set up their own environment
 
1215
        (including self.server) and explicitely call this
 
1216
        method. This is needed because we want to reuse the same
 
1217
        tests for proxy and no-proxy accesses which have
 
1218
        different ways of setting self.server.
 
1219
        """
 
1220
        self.build_tree_contents([('a', 'contents of a\n'),
 
1221
                                  ('b', 'contents of b\n'),])
 
1222
        self.old_factory = ui.ui_factory
 
1223
        # The following has the unfortunate side-effect of hiding any ouput
 
1224
        # during the tests (including pdb prompts). Feel free to comment them
 
1225
        # for debugging purposes but leave them in place, there are needed to
 
1226
        # run the tests without any console
 
1227
        self.old_stdout = sys.stdout
 
1228
        sys.stdout = StringIOWrapper()
 
1229
        self.addCleanup(self.restoreUIFactory)
 
1230
 
 
1231
    def restoreUIFactory(self):
 
1232
        ui.ui_factory = self.old_factory
 
1233
        sys.stdout = self.old_stdout
 
1234
 
 
1235
    def get_user_url(self, user=None, password=None):
 
1236
        """Build an url embedding user and password"""
 
1237
        url = '%s://' % self.server._url_protocol
 
1238
        if user is not None:
 
1239
            url += user
 
1240
            if password is not None:
 
1241
                url += ':' + password
 
1242
            url += '@'
 
1243
        url += '%s:%s/' % (self.server.host, self.server.port)
 
1244
        return url
 
1245
 
 
1246
    def test_no_user(self):
 
1247
        self.server.add_user('joe', 'foo')
 
1248
        t = self.get_user_transport()
 
1249
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
 
1250
        # Only one 'Authentication Required' error should occur
 
1251
        self.assertEqual(1, self.server.auth_required_errors)
 
1252
 
 
1253
    def test_empty_pass(self):
 
1254
        self.server.add_user('joe', '')
 
1255
        t = self.get_user_transport('joe', '')
 
1256
        self.assertEqual('contents of a\n', t.get('a').read())
 
1257
        # Only one 'Authentication Required' error should occur
 
1258
        self.assertEqual(1, self.server.auth_required_errors)
 
1259
 
 
1260
    def test_user_pass(self):
 
1261
        self.server.add_user('joe', 'foo')
 
1262
        t = self.get_user_transport('joe', 'foo')
 
1263
        self.assertEqual('contents of a\n', t.get('a').read())
 
1264
        # Only one 'Authentication Required' error should occur
 
1265
        self.assertEqual(1, self.server.auth_required_errors)
 
1266
 
 
1267
    def test_unknown_user(self):
 
1268
        self.server.add_user('joe', 'foo')
 
1269
        t = self.get_user_transport('bill', 'foo')
 
1270
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
 
1271
        # Two 'Authentication Required' errors should occur (the
 
1272
        # initial 'who are you' and 'I don't know you, who are
 
1273
        # you').
 
1274
        self.assertEqual(2, self.server.auth_required_errors)
 
1275
 
 
1276
    def test_wrong_pass(self):
 
1277
        self.server.add_user('joe', 'foo')
 
1278
        t = self.get_user_transport('joe', 'bar')
 
1279
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
 
1280
        # Two 'Authentication Required' errors should occur (the
 
1281
        # initial 'who are you' and 'this is not you, who are you')
 
1282
        self.assertEqual(2, self.server.auth_required_errors)
 
1283
 
 
1284
    def test_prompt_for_password(self):
 
1285
        self.server.add_user('joe', 'foo')
 
1286
        t = self.get_user_transport('joe', None)
 
1287
        ui.ui_factory = TestUIFactory(stdin='foo\n')
 
1288
        self.assertEqual('contents of a\n',t.get('a').read())
 
1289
        # stdin should be empty
 
1290
        self.assertEqual('', ui.ui_factory.stdin.readline())
 
1291
        # And we shouldn't prompt again for a different request
 
1292
        # against the same transport.
 
1293
        self.assertEqual('contents of b\n',t.get('b').read())
 
1294
        t2 = t.clone()
 
1295
        # And neither against a clone
 
1296
        self.assertEqual('contents of b\n',t2.get('b').read())
 
1297
        # Only one 'Authentication Required' error should occur
 
1298
        self.assertEqual(1, self.server.auth_required_errors)
 
1299
 
 
1300
 
 
1301
class TestHTTPAuth(TestAuth):
 
1302
    """Test HTTP authentication schemes.
 
1303
 
 
1304
    Daughter classes MUST inherit from TestCaseWithWebserver too.
 
1305
    """
 
1306
 
 
1307
    _auth_header = 'Authorization'
 
1308
 
 
1309
    def setUp(self):
 
1310
        TestCaseWithWebserver.setUp(self)
 
1311
        self.server = self.get_readonly_server()
 
1312
        TestAuth.setUp(self)
 
1313
 
 
1314
    def get_user_transport(self, user=None, password=None):
 
1315
        return self._transport(self.get_user_url(user, password))
 
1316
 
 
1317
 
 
1318
class TestProxyAuth(TestAuth):
 
1319
    """Test proxy authentication schemes.
 
1320
 
 
1321
    Daughter classes MUST also inherit from TestCaseWithWebserver.
 
1322
    """
 
1323
    _auth_header = 'Proxy-authorization'
 
1324
 
 
1325
    def setUp(self):
 
1326
        TestCaseWithWebserver.setUp(self)
 
1327
        self.server = self.get_readonly_server()
 
1328
        self._old_env = {}
 
1329
        self.addCleanup(self._restore_env)
 
1330
        TestAuth.setUp(self)
 
1331
        # Override the contents to avoid false positives
 
1332
        self.build_tree_contents([('a', 'not proxied contents of a\n'),
 
1333
                                  ('b', 'not proxied contents of b\n'),
 
1334
                                  ('a-proxied', 'contents of a\n'),
 
1335
                                  ('b-proxied', 'contents of b\n'),
 
1336
                                  ])
 
1337
 
 
1338
    def get_user_transport(self, user=None, password=None):
 
1339
        self._install_env({'all_proxy': self.get_user_url(user, password)})
 
1340
        return self._transport(self.server.get_url())
 
1341
 
 
1342
    def _install_env(self, env):
 
1343
        for name, value in env.iteritems():
 
1344
            self._old_env[name] = osutils.set_or_unset_env(name, value)
 
1345
 
 
1346
    def _restore_env(self):
 
1347
        for name, value in self._old_env.iteritems():
 
1348
            osutils.set_or_unset_env(name, value)
 
1349
 
 
1350
 
 
1351
class TestHTTPBasicAuth(TestHTTPAuth, TestCaseWithWebserver):
 
1352
    """Test http basic authentication scheme"""
 
1353
 
 
1354
    _transport = HttpTransport_urllib
 
1355
 
 
1356
    def create_transport_readonly_server(self):
 
1357
        return HTTPBasicAuthServer()
 
1358
 
 
1359
 
 
1360
class TestHTTPProxyBasicAuth(TestProxyAuth, TestCaseWithWebserver):
 
1361
    """Test proxy basic authentication scheme"""
 
1362
 
 
1363
    _transport = HttpTransport_urllib
 
1364
 
 
1365
    def create_transport_readonly_server(self):
 
1366
        return ProxyBasicAuthServer()
 
1367
 
 
1368
 
 
1369
class TestDigestAuth(object):
 
1370
    """Digest Authentication specific tests"""
 
1371
 
 
1372
    def test_changing_nonce(self):
 
1373
        self.server.add_user('joe', 'foo')
 
1374
        t = self.get_user_transport('joe', 'foo')
 
1375
        self.assertEqual('contents of a\n', t.get('a').read())
 
1376
        self.assertEqual('contents of b\n', t.get('b').read())
 
1377
        # Only one 'Authentication Required' error should have
 
1378
        # occured so far
 
1379
        self.assertEqual(1, self.server.auth_required_errors)
 
1380
        # The server invalidates the current nonce
 
1381
        self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
 
1382
        self.assertEqual('contents of a\n', t.get('a').read())
 
1383
        # Two 'Authentication Required' errors should occur (the
 
1384
        # initial 'who are you' and a second 'who are you' with the new nonce)
 
1385
        self.assertEqual(2, self.server.auth_required_errors)
 
1386
 
 
1387
 
 
1388
class TestHTTPDigestAuth(TestHTTPAuth, TestDigestAuth, TestCaseWithWebserver):
 
1389
    """Test http digest authentication scheme"""
 
1390
 
 
1391
    _transport = HttpTransport_urllib
 
1392
 
 
1393
    def create_transport_readonly_server(self):
 
1394
        return HTTPDigestAuthServer()
 
1395
 
 
1396
 
 
1397
class TestHTTPProxyDigestAuth(TestProxyAuth, TestDigestAuth,
 
1398
                              TestCaseWithWebserver):
 
1399
    """Test proxy digest authentication scheme"""
 
1400
 
 
1401
    _transport = HttpTransport_urllib
 
1402
 
 
1403
    def create_transport_readonly_server(self):
 
1404
        return ProxyDigestAuthServer()
 
1405