Merge lp:~cjwatson/launchpad/librarianserver-test-web-requests into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18811
Proposed branch: lp:~cjwatson/launchpad/librarianserver-test-web-requests
Merge into: lp:launchpad
Diff against target: 423 lines (+90/-92)
1 file modified
lib/lp/services/librarianserver/tests/test_web.py (+90/-92)
To merge this branch: bzr merge lp:~cjwatson/launchpad/librarianserver-test-web-requests
Reviewer Review Type Date Requested Status
Johan Dahlin (community) Approve
Launchpad code reviewers Pending
Review via email: mp+358189@code.launchpad.net

Commit message

Convert lp.services.librarianserver.tests.test_web to requests.

Description of the change

Extracted from https://code.launchpad.net/~cjwatson/launchpad/librarian-accept-macaroon/+merge/345079 to make the review diff size more manageable.

To post a comment you must log in.
Revision history for this message
Johan Dahlin (jdahlin-deactivatedaccount) wrote :

Two nitpicks, looks good either way.

review: Approve
Revision history for this message
Colin Watson (cjwatson) :
Revision history for this message
Johan Dahlin (jdahlin-deactivatedaccount) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/services/librarianserver/tests/test_web.py'
--- lib/lp/services/librarianserver/tests/test_web.py 2018-01-02 10:54:31 +0000
+++ lib/lp/services/librarianserver/tests/test_web.py 2018-11-02 10:16:17 +0000
@@ -1,20 +1,18 @@
1# Copyright 2009-2016 Canonical Ltd. This software is licensed under the1# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4from cStringIO import StringIO
5from datetime import datetime4from datetime import datetime
5from gzip import GzipFile
6import hashlib6import hashlib
7import httplib7import httplib
8from io import BytesIO
8import os9import os
9import unittest10import unittest
10from urllib2 import (
11 HTTPError,
12 urlopen,
13 )
14from urlparse import urlparse11from urlparse import urlparse
1512
16from lazr.uri import URI13from lazr.uri import URI
17import pytz14import pytz
15import requests
18from storm.expr import SQL16from storm.expr import SQL
19import testtools17import testtools
20from testtools.matchers import EndsWith18from testtools.matchers import EndsWith
@@ -55,7 +53,6 @@
55class LibrarianWebTestCase(testtools.TestCase):53class LibrarianWebTestCase(testtools.TestCase):
56 """Test the librarian's web interface."""54 """Test the librarian's web interface."""
57 layer = LaunchpadFunctionalLayer55 layer = LaunchpadFunctionalLayer
58 dbuser = 'librarian'
5956
60 # Add stuff to a librarian via the upload port, then check that it's57 # Add stuff to a librarian via the upload port, then check that it's
61 # immediately visible on the web interface. (in an attempt to test ddaa's58 # immediately visible on the web interface. (in an attempt to test ddaa's
@@ -75,9 +72,9 @@
75 for count in range(10):72 for count in range(10):
76 # Upload a file. This should work without any exceptions being73 # Upload a file. This should work without any exceptions being
77 # thrown.74 # thrown.
78 sampleData = 'x' + ('blah' * (count % 5))75 sampleData = b'x' + (b'blah' * (count % 5))
79 fileAlias = client.addFile('sample', len(sampleData),76 fileAlias = client.addFile('sample', len(sampleData),
80 StringIO(sampleData),77 BytesIO(sampleData),
81 contentType='text/plain')78 contentType='text/plain')
8279
83 # Make sure we can get its URL80 # Make sure we can get its URL
@@ -98,9 +95,9 @@
98 fileObj.close()95 fileObj.close()
9996
100 # And make sure the URL works too97 # And make sure the URL works too
101 fileObj = urlopen(url)98 response = requests.get(url)
102 self.assertEqual(sampleData, fileObj.read())99 response.raise_for_status()
103 fileObj.close()100 self.assertEqual(sampleData, response.content)
104101
105 def test_checkGzipEncoding(self):102 def test_checkGzipEncoding(self):
106 # Files that end in ".txt.gz" are treated special and are returned103 # Files that end in ".txt.gz" are treated special and are returned
@@ -108,29 +105,34 @@
108 # displaying Ubuntu build logs in the browser. The mimetype should be105 # displaying Ubuntu build logs in the browser. The mimetype should be
109 # "text/plain" for these files.106 # "text/plain" for these files.
110 client = LibrarianClient()107 client = LibrarianClient()
111 contents = 'Build log...'108 contents = u'Build log \N{SNOWMAN}...'.encode('UTF-8')
112 build_log = StringIO(contents)109 build_log = BytesIO()
110 with GzipFile(mode='wb', fileobj=build_log) as f:
111 f.write(contents)
112 build_log.seek(0)
113 alias_id = client.addFile(name="build_log.txt.gz",113 alias_id = client.addFile(name="build_log.txt.gz",
114 size=len(contents),114 size=len(build_log.getvalue()),
115 file=build_log,115 file=build_log,
116 contentType="text/plain")116 contentType="text/plain")
117117
118 self.commit()118 self.commit()
119119
120 url = client.getURLForAlias(alias_id)120 url = client.getURLForAlias(alias_id)
121 fileObj = urlopen(url)121 response = requests.get(url)
122 mimetype = fileObj.headers['content-type']122 response.raise_for_status()
123 encoding = fileObj.headers['content-encoding']123 mimetype = response.headers['content-type']
124 encoding = response.headers['content-encoding']
124 self.assertTrue(mimetype == "text/plain; charset=utf-8",125 self.assertTrue(mimetype == "text/plain; charset=utf-8",
125 "Wrong mimetype. %s != 'text/plain'." % mimetype)126 "Wrong mimetype. %s != 'text/plain'." % mimetype)
126 self.assertTrue(encoding == "gzip",127 self.assertTrue(encoding == "gzip",
127 "Wrong encoding. %s != 'gzip'." % encoding)128 "Wrong encoding. %s != 'gzip'." % encoding)
129 self.assertEqual(contents.decode('UTF-8'), response.text)
128130
129 def test_checkNoEncoding(self):131 def test_checkNoEncoding(self):
130 # Other files should have no encoding.132 # Other files should have no encoding.
131 client = LibrarianClient()133 client = LibrarianClient()
132 contents = 'Build log...'134 contents = b'Build log...'
133 build_log = StringIO(contents)135 build_log = BytesIO(contents)
134 alias_id = client.addFile(name="build_log.tgz",136 alias_id = client.addFile(name="build_log.tgz",
135 size=len(contents),137 size=len(contents),
136 file=build_log,138 file=build_log,
@@ -139,10 +141,10 @@
139 self.commit()141 self.commit()
140142
141 url = client.getURLForAlias(alias_id)143 url = client.getURLForAlias(alias_id)
142 fileObj = urlopen(url)144 response = requests.get(url)
143 mimetype = fileObj.headers['content-type']145 response.raise_for_status()
144 self.assertRaises(KeyError, fileObj.headers.__getitem__,146 mimetype = response.headers['content-type']
145 'content-encoding')147 self.assertNotIn('content-encoding', response.headers)
146 self.assertTrue(148 self.assertTrue(
147 mimetype == "application/x-tar",149 mimetype == "application/x-tar",
148 "Wrong mimetype. %s != 'application/x-tar'." % mimetype)150 "Wrong mimetype. %s != 'application/x-tar'." % mimetype)
@@ -157,13 +159,17 @@
157 # ignored159 # ignored
158 client = LibrarianClient()160 client = LibrarianClient()
159 filename = 'sample.txt'161 filename = 'sample.txt'
160 aid = client.addFile(filename, 6, StringIO('sample'), 'text/plain')162 aid = client.addFile(filename, 6, BytesIO(b'sample'), 'text/plain')
161 self.commit()163 self.commit()
162 url = client.getURLForAlias(aid)164 url = client.getURLForAlias(aid)
163 self.assertEqual(urlopen(url).read(), 'sample')165 response = requests.get(url)
166 response.raise_for_status()
167 self.assertEqual(response.content, b'sample')
164168
165 old_url = uri_path_replace(url, str(aid), '42/%d' % aid)169 old_url = uri_path_replace(url, str(aid), '42/%d' % aid)
166 self.assertEqual(urlopen(old_url).read(), 'sample')170 response = requests.get(url)
171 response.raise_for_status()
172 self.assertEqual(response.content, b'sample')
167173
168 # If the content and alias IDs are not integers, a 404 is raised174 # If the content and alias IDs are not integers, a 404 is raised
169 old_url = uri_path_replace(url, str(aid), 'foo/%d' % aid)175 old_url = uri_path_replace(url, str(aid), 'foo/%d' % aid)
@@ -174,10 +180,12 @@
174 def test_404(self):180 def test_404(self):
175 client = LibrarianClient()181 client = LibrarianClient()
176 filename = 'sample.txt'182 filename = 'sample.txt'
177 aid = client.addFile(filename, 6, StringIO('sample'), 'text/plain')183 aid = client.addFile(filename, 6, BytesIO(b'sample'), 'text/plain')
178 self.commit()184 self.commit()
179 url = client.getURLForAlias(aid)185 url = client.getURLForAlias(aid)
180 self.assertEqual(urlopen(url).read(), 'sample')186 response = requests.get(url)
187 response.raise_for_status()
188 self.assertEqual(response.content, b'sample')
181189
182 # Change the aliasid and assert we get a 404190 # Change the aliasid and assert we get a 404
183 self.assertIn(str(aid), url)191 self.assertIn(str(aid), url)
@@ -192,29 +200,30 @@
192 def test_duplicateuploads(self):200 def test_duplicateuploads(self):
193 client = LibrarianClient()201 client = LibrarianClient()
194 filename = 'sample.txt'202 filename = 'sample.txt'
195 id1 = client.addFile(filename, 6, StringIO('sample'), 'text/plain')203 id1 = client.addFile(filename, 6, BytesIO(b'sample'), 'text/plain')
196 id2 = client.addFile(filename, 6, StringIO('sample'), 'text/plain')204 id2 = client.addFile(filename, 6, BytesIO(b'sample'), 'text/plain')
197205
198 self.assertNotEqual(id1, id2, 'Got allocated the same id!')206 self.assertNotEqual(id1, id2, 'Got allocated the same id!')
199207
200 self.commit()208 self.commit()
201209
202 self.assertEqual(client.getFileByAlias(id1).read(), 'sample')210 self.assertEqual(client.getFileByAlias(id1).read(), b'sample')
203 self.assertEqual(client.getFileByAlias(id2).read(), 'sample')211 self.assertEqual(client.getFileByAlias(id2).read(), b'sample')
204212
205 def test_robotsTxt(self):213 def test_robotsTxt(self):
206 url = 'http://%s:%d/robots.txt' % (214 url = 'http://%s:%d/robots.txt' % (
207 config.librarian.download_host, config.librarian.download_port)215 config.librarian.download_host, config.librarian.download_port)
208 f = urlopen(url)216 response = requests.get(url)
209 self.assertIn('Disallow: /', f.read())217 response.raise_for_status()
218 self.assertIn('Disallow: /', response.text)
210219
211 def test_headers(self):220 def test_headers(self):
212 client = LibrarianClient()221 client = LibrarianClient()
213222
214 # Upload a file so we can retrieve it.223 # Upload a file so we can retrieve it.
215 sample_data = 'blah'224 sample_data = b'blah'
216 file_alias_id = client.addFile(225 file_alias_id = client.addFile(
217 'sample', len(sample_data), StringIO(sample_data),226 'sample', len(sample_data), BytesIO(sample_data),
218 contentType='text/plain')227 contentType='text/plain')
219 url = client.getURLForAlias(file_alias_id)228 url = client.getURLForAlias(file_alias_id)
220229
@@ -229,9 +238,10 @@
229 self.commit()238 self.commit()
230239
231 # Fetch the file via HTTP, recording the interesting headers240 # Fetch the file via HTTP, recording the interesting headers
232 result = urlopen(url)241 response = requests.get(url)
233 last_modified_header = result.info()['Last-Modified']242 response.raise_for_status()
234 cache_control_header = result.info()['Cache-Control']243 last_modified_header = response.headers['Last-Modified']
244 cache_control_header = response.headers['Cache-Control']
235245
236 # URLs point to the same content for ever, so we have a hardcoded246 # URLs point to the same content for ever, so we have a hardcoded
237 # 1 year max-age cache policy.247 # 1 year max-age cache policy.
@@ -247,9 +257,9 @@
247 client = LibrarianClient()257 client = LibrarianClient()
248258
249 # Upload a file so we can retrieve it.259 # Upload a file so we can retrieve it.
250 sample_data = 'blah'260 sample_data = b'blah'
251 file_alias_id = client.addFile(261 file_alias_id = client.addFile(
252 'sample', len(sample_data), StringIO(sample_data),262 'sample', len(sample_data), BytesIO(sample_data),
253 contentType='text/plain')263 contentType='text/plain')
254 url = client.getURLForAlias(file_alias_id)264 url = client.getURLForAlias(file_alias_id)
255265
@@ -262,18 +272,19 @@
262 self.commit()272 self.commit()
263273
264 # Fetch the file via HTTP.274 # Fetch the file via HTTP.
265 urlopen(url)275 response = requests.get(url)
276 response.raise_for_status()
266277
267 # Delete the on-disk file.278 # Delete the on-disk file.
268 storage = LibrarianStorage(config.librarian_server.root, None)279 storage = LibrarianStorage(config.librarian_server.root, None)
269 os.remove(storage._fileLocation(file_alias.contentID))280 os.remove(storage._fileLocation(file_alias.contentID))
270281
271 # The URL now 500s, since the DB says it should exist.282 # The URL now 500s, since the DB says it should exist.
272 exception = self.assertRaises(HTTPError, urlopen, url)283 response = requests.get(url)
273 self.assertEqual(500, exception.code)284 self.assertEqual(500, response.status_code)
274 self.assertIn('Server', exception.info())285 self.assertIn('Server', response.headers)
275 self.assertNotIn('Last-Modified', exception.info())286 self.assertNotIn('Last-Modified', response.headers)
276 self.assertNotIn('Cache-Control', exception.info())287 self.assertNotIn('Cache-Control', response.headers)
277288
278 def get_restricted_file_and_public_url(self, filename='sample'):289 def get_restricted_file_and_public_url(self, filename='sample'):
279 # Use a regular LibrarianClient to ensure we speak to the290 # Use a regular LibrarianClient to ensure we speak to the
@@ -281,10 +292,10 @@
281 # restricted files are served from.292 # restricted files are served from.
282 client = LibrarianClient()293 client = LibrarianClient()
283 fileAlias = client.addFile(294 fileAlias = client.addFile(
284 filename, 12, StringIO('a' * 12), contentType='text/plain')295 filename, 12, BytesIO(b'a' * 12), contentType='text/plain')
285 # Note: We're deliberately using the wrong url here: we should be296 # Note: We're deliberately using the wrong url here: we should be
286 # passing secure=True to getURLForAlias, but to use the returned URL297 # passing secure=True to getURLForAlias, but to use the returned URL
287 # we would need a wildcard DNS facility patched into urlopen; instead298 # we would need a wildcard DNS facility patched into requests; instead
288 # we use the *deliberate* choice of having the path of secure and299 # we use the *deliberate* choice of having the path of secure and
289 # insecure urls be the same, so that we can test it: the server code300 # insecure urls be the same, so that we can test it: the server code
290 # doesn't need to know about the fancy wildcard domains.301 # doesn't need to know about the fancy wildcard domains.
@@ -301,9 +312,9 @@
301 # IFF there is a .restricted. in the host, then the library file alias312 # IFF there is a .restricted. in the host, then the library file alias
302 # in the subdomain must match that in the path.313 # in the subdomain must match that in the path.
303 client = LibrarianClient()314 client = LibrarianClient()
304 fileAlias = client.addFile('sample', 12, StringIO('a' * 12),315 fileAlias = client.addFile('sample', 12, BytesIO(b'a' * 12),
305 contentType='text/plain')316 contentType='text/plain')
306 fileAlias2 = client.addFile('sample', 12, StringIO('b' * 12),317 fileAlias2 = client.addFile('sample', 12, BytesIO(b'b' * 12),
307 contentType='text/plain')318 contentType='text/plain')
308 self.commit()319 self.commit()
309 url = client.getURLForAlias(fileAlias)320 url = client.getURLForAlias(fileAlias)
@@ -313,7 +324,8 @@
313 template_host = 'i%%d.restricted.%s' % download_host324 template_host = 'i%%d.restricted.%s' % download_host
314 path = get_libraryfilealias_download_path(fileAlias, 'sample')325 path = get_libraryfilealias_download_path(fileAlias, 'sample')
315 # The basic URL must work.326 # The basic URL must work.
316 urlopen(url)327 response = requests.get(url)
328 response.raise_for_status()
317 # Use the network level protocol because DNS resolution won't work329 # Use the network level protocol because DNS resolution won't work
318 # here (no wildcard support)330 # here (no wildcard support)
319 connection = httplib.HTTPConnection(331 connection = httplib.HTTPConnection(
@@ -356,20 +368,17 @@
356 fileAlias, url = self.get_restricted_file_and_public_url()368 fileAlias, url = self.get_restricted_file_and_public_url()
357 # The file should not be able to be opened - the token supplied369 # The file should not be able to be opened - the token supplied
358 # is not one we issued.370 # is not one we issued.
359 self.require404(url + '?token=haxx0r')371 self.require404(url, params={"token": "haxx0r"})
360372
361 def test_restricted_with_token(self):373 def test_restricted_with_token(self):
362 fileAlias, url = self.get_restricted_file_and_public_url()374 fileAlias, url = self.get_restricted_file_and_public_url()
363 # We have the base url for a restricted file; grant access to it375 # We have the base url for a restricted file; grant access to it
364 # for a short time.376 # for a short time.
365 token = TimeLimitedToken.allocate(url)377 token = TimeLimitedToken.allocate(url)
366 url = url + "?token=%s" % token
367 # Now we should be able to access the file.378 # Now we should be able to access the file.
368 fileObj = urlopen(url)379 response = requests.get(url, params={"token": token})
369 try:380 response.raise_for_status()
370 self.assertEqual("a" * 12, fileObj.read())381 self.assertEqual(b"a" * 12, response.content)
371 finally:
372 fileObj.close()
373382
374 def test_restricted_with_token_encoding(self):383 def test_restricted_with_token_encoding(self):
375 fileAlias, url = self.get_restricted_file_and_public_url('foo~%')384 fileAlias, url = self.get_restricted_file_and_public_url('foo~%')
@@ -380,20 +389,16 @@
380 token = TimeLimitedToken.allocate(url)389 token = TimeLimitedToken.allocate(url)
381390
382 # Now we should be able to access the file.391 # Now we should be able to access the file.
383 fileObj = urlopen(url + "?token=%s" % token)392 response = requests.get(url, params={"token": token})
384 try:393 response.raise_for_status()
385 self.assertEqual("a" * 12, fileObj.read())394 self.assertEqual(b"a" * 12, response.content)
386 finally:
387 fileObj.close()
388395
389 # The token is valid even if the filename is encoded differently.396 # The token is valid even if the filename is encoded differently.
390 mangled_url = url.replace('~', '%7E')397 mangled_url = url.replace('~', '%7E')
391 self.assertNotEqual(mangled_url, url)398 self.assertNotEqual(mangled_url, url)
392 fileObj = urlopen(mangled_url + "?token=%s" % token)399 response = requests.get(url, params={"token": token})
393 try:400 response.raise_for_status()
394 self.assertEqual("a" * 12, fileObj.read())401 self.assertEqual(b"a" * 12, response.content)
395 finally:
396 fileObj.close()
397402
398 def test_restricted_with_expired_token(self):403 def test_restricted_with_expired_token(self):
399 fileAlias, url = self.get_restricted_file_and_public_url()404 fileAlias, url = self.get_restricted_file_and_public_url()
@@ -407,14 +412,12 @@
407 TimeLimitedToken.token == hashlib.sha256(token).hexdigest())412 TimeLimitedToken.token == hashlib.sha256(token).hexdigest())
408 tokens.set(413 tokens.set(
409 TimeLimitedToken.created == SQL("created - interval '1 week'"))414 TimeLimitedToken.created == SQL("created - interval '1 week'"))
410 url = url + "?token=%s" % token
411 # Now, as per test_restricted_no_token we should get a 404.415 # Now, as per test_restricted_no_token we should get a 404.
412 self.require404(url)416 self.require404(url, params={"token": token})
413417
414 def test_restricted_file_headers(self):418 def test_restricted_file_headers(self):
415 fileAlias, url = self.get_restricted_file_and_public_url()419 fileAlias, url = self.get_restricted_file_and_public_url()
416 token = TimeLimitedToken.allocate(url)420 token = TimeLimitedToken.allocate(url)
417 url = url + "?token=%s" % token
418 # Change the date_created to a known value for testing.421 # Change the date_created to a known value for testing.
419 file_alias = IMasterStore(LibraryFileAlias).get(422 file_alias = IMasterStore(LibraryFileAlias).get(
420 LibraryFileAlias, fileAlias)423 LibraryFileAlias, fileAlias)
@@ -423,9 +426,9 @@
423 # Commit the update.426 # Commit the update.
424 self.commit()427 self.commit()
425 # Fetch the file via HTTP, recording the interesting headers428 # Fetch the file via HTTP, recording the interesting headers
426 result = urlopen(url)429 response = requests.get(url, params={"token": token})
427 last_modified_header = result.info()['Last-Modified']430 last_modified_header = response.headers['Last-Modified']
428 cache_control_header = result.info()['Cache-Control']431 cache_control_header = response.headers['Cache-Control']
429 # No caching for restricted files.432 # No caching for restricted files.
430 self.assertEqual(cache_control_header, 'max-age=0, private')433 self.assertEqual(cache_control_header, 'max-age=0, private')
431 # And we should have a correct Last-Modified header too.434 # And we should have a correct Last-Modified header too.
@@ -433,13 +436,10 @@
433 last_modified_header, 'Tue, 30 Jan 2001 13:45:59 GMT')436 last_modified_header, 'Tue, 30 Jan 2001 13:45:59 GMT')
434 # Perhaps we should also set Expires to the Last-Modified.437 # Perhaps we should also set Expires to the Last-Modified.
435438
436 def require404(self, url):439 def require404(self, url, **kwargs):
437 """Assert that opening `url` raises a 404."""440 """Assert that opening `url` raises a 404."""
438 try:441 response = requests.get(url, **kwargs)
439 urlopen(url)442 self.assertEqual(404, response.status_code)
440 self.fail('404 not raised')
441 except HTTPError as e:
442 self.assertEqual(e.code, 404)
443443
444444
445class LibrarianZopelessWebTestCase(LibrarianWebTestCase):445class LibrarianZopelessWebTestCase(LibrarianWebTestCase):
@@ -455,9 +455,9 @@
455 def test_getURLForAliasObject(self):455 def test_getURLForAliasObject(self):
456 # getURLForAliasObject returns the same URL as getURLForAlias.456 # getURLForAliasObject returns the same URL as getURLForAlias.
457 client = LibrarianClient()457 client = LibrarianClient()
458 content = "Test content"458 content = b"Test content"
459 alias_id = client.addFile(459 alias_id = client.addFile(
460 'test.txt', len(content), StringIO(content),460 'test.txt', len(content), BytesIO(content),
461 contentType='text/plain')461 contentType='text/plain')
462 self.commit()462 self.commit()
463463
@@ -481,7 +481,7 @@
481 switch_dbuser('testadmin')481 switch_dbuser('testadmin')
482482
483 alias = getUtility(ILibraryFileAliasSet).create(483 alias = getUtility(ILibraryFileAliasSet).create(
484 'whatever', 8, StringIO('xxx\nxxx\n'), 'text/plain')484 'whatever', 8, BytesIO(b'xxx\nxxx\n'), 'text/plain')
485 alias_id = alias.id485 alias_id = alias.id
486 transaction.commit()486 transaction.commit()
487487
@@ -493,8 +493,9 @@
493493
494 # And it can be retrieved via the web494 # And it can be retrieved via the web
495 url = alias.http_url495 url = alias.http_url
496 retrieved_content = urlopen(url).read()496 response = requests.get(url)
497 self.assertEqual(retrieved_content, 'xxx\nxxx\n')497 response.raise_for_status()
498 self.assertEqual(response.content, b'xxx\nxxx\n')
498499
499 # But when we flag the content as deleted500 # But when we flag the content as deleted
500 cur = cursor()501 cur = cursor()
@@ -508,8 +509,5 @@
508 self.assertRaises(DownloadFailed, alias.open)509 self.assertRaises(DownloadFailed, alias.open)
509510
510 # And people see a 404 page511 # And people see a 404 page
511 try:512 response = requests.get(url)
512 urlopen(url)513 self.assertEqual(404, response.status_code)
513 self.fail('404 not raised')
514 except HTTPError as x:
515 self.assertEqual(x.code, 404)