Michael Merickel
2018-10-17 e14661417e7ceb50d5cf83bbd6abd6b133e473ba
commit | author | age
64aaf0 1 import datetime
3a4119 2 import os.path
d809ac 3 import unittest
3a4119 4
MM 5 here = os.path.dirname(__file__)
64aaf0 6
CM 7 # 5 years from now (more or less)
0c29cf 8 fiveyrsfuture = datetime.datetime.utcnow() + datetime.timedelta(5 * 365)
MM 9
d809ac 10
f2ef79 11 class Test_static_view_use_subpath_False(unittest.TestCase):
d809ac 12     def _getTargetClass(self):
f2ef79 13         from pyramid.static import static_view
0c29cf 14
f2ef79 15         return static_view
d809ac 16
CM 17     def _makeOne(self, *arg, **kw):
18         return self._getTargetClass()(*arg, **kw)
19
f2ef79 20     def _makeRequest(self, kw=None):
CM 21         from pyramid.request import Request
0c29cf 22
d809ac 23         environ = {
0c29cf 24             'wsgi.url_scheme': 'http',
MM 25             'wsgi.version': (1, 0),
26             'SERVER_NAME': 'example.com',
27             'SERVER_PORT': '6543',
28             'PATH_INFO': '/',
29             'SCRIPT_NAME': '',
30             'REQUEST_METHOD': 'GET',
31         }
f2ef79 32         if kw is not None:
CM 33             environ.update(kw)
34         return Request(environ=environ)
eac3ff 35
d809ac 36     def test_ctor_defaultargs(self):
f2ef79 37         inst = self._makeOne('package:resource_name')
d809ac 38         self.assertEqual(inst.package_name, 'package')
f2ef79 39         self.assertEqual(inst.docroot, 'resource_name')
fe1548 40         self.assertEqual(inst.cache_max_age, 3600)
f2ef79 41         self.assertEqual(inst.index, 'index.html')
d809ac 42
CM 43     def test_call_adds_slash_path_info_empty(self):
dd3cc8 44         inst = self._makeOne('tests:fixtures/static')
0c29cf 45         request = self._makeRequest({'PATH_INFO': ''})
f2ef79 46         context = DummyContext()
3d65af 47         from pyramid.httpexceptions import HTTPMovedPermanently
0c29cf 48
e7c4dd 49         self.assertRaises(HTTPMovedPermanently, inst, context, request)
3d65af 50
d809ac 51     def test_path_info_slash_means_index_html(self):
dd3cc8 52         inst = self._makeOne('tests:fixtures/static')
f2ef79 53         request = self._makeRequest()
CM 54         context = DummyContext()
55         response = inst(context, request)
8e606d 56         self.assertTrue(b'<html>static</html>' in response.body)
d809ac 57
05b8a6 58     def test_oob_singledot(self):
dd3cc8 59         inst = self._makeOne('tests:fixtures/static')
0c29cf 60         request = self._makeRequest({'PATH_INFO': '/./index.html'})
05b8a6 61         context = DummyContext()
CM 62         response = inst(context, request)
63         self.assertEqual(response.status, '200 OK')
8e606d 64         self.assertTrue(b'<html>static</html>' in response.body)
05b8a6 65
CM 66     def test_oob_emptyelement(self):
dd3cc8 67         inst = self._makeOne('tests:fixtures/static')
0c29cf 68         request = self._makeRequest({'PATH_INFO': '//index.html'})
05b8a6 69         context = DummyContext()
CM 70         response = inst(context, request)
71         self.assertEqual(response.status, '200 OK')
8e606d 72         self.assertTrue(b'<html>static</html>' in response.body)
05b8a6 73
CM 74     def test_oob_dotdotslash(self):
dd3cc8 75         inst = self._makeOne('tests:fixtures/static')
0c29cf 76         request = self._makeRequest({'PATH_INFO': '/subdir/../../minimal.pt'})
05b8a6 77         context = DummyContext()
04af14 78         from pyramid.httpexceptions import HTTPNotFound
0c29cf 79
e7c4dd 80         self.assertRaises(HTTPNotFound, inst, context, request)
05b8a6 81
CM 82     def test_oob_dotdotslash_encoded(self):
dd3cc8 83         inst = self._makeOne('tests:fixtures/static')
05b8a6 84         request = self._makeRequest(
0c29cf 85             {'PATH_INFO': '/subdir/%2E%2E%2F%2E%2E/minimal.pt'}
MM 86         )
05b8a6 87         context = DummyContext()
04af14 88         from pyramid.httpexceptions import HTTPNotFound
0c29cf 89
e7c4dd 90         self.assertRaises(HTTPNotFound, inst, context, request)
05b8a6 91
CM 92     def test_oob_os_sep(self):
93         import os
0c29cf 94
dd3cc8 95         inst = self._makeOne('tests:fixtures/static')
05b8a6 96         dds = '..' + os.sep
0c29cf 97         request = self._makeRequest(
MM 98             {'PATH_INFO': '/subdir/%s%sminimal.pt' % (dds, dds)}
99         )
f2ef79 100         context = DummyContext()
04af14 101         from pyramid.httpexceptions import HTTPNotFound
0c29cf 102
e7c4dd 103         self.assertRaises(HTTPNotFound, inst, context, request)
d809ac 104
CM 105     def test_resource_doesnt_exist(self):
dd3cc8 106         inst = self._makeOne('tests:fixtures/static')
0c29cf 107         request = self._makeRequest({'PATH_INFO': '/notthere'})
f2ef79 108         context = DummyContext()
04af14 109         from pyramid.httpexceptions import HTTPNotFound
0c29cf 110
e7c4dd 111         self.assertRaises(HTTPNotFound, inst, context, request)
d809ac 112
CM 113     def test_resource_isdir(self):
dd3cc8 114         inst = self._makeOne('tests:fixtures/static')
0c29cf 115         request = self._makeRequest({'PATH_INFO': '/subdir/'})
f2ef79 116         context = DummyContext()
CM 117         response = inst(context, request)
8e606d 118         self.assertTrue(b'<html>subdir</html>' in response.body)
d809ac 119
CM 120     def test_resource_is_file(self):
dd3cc8 121         inst = self._makeOne('tests:fixtures/static')
0c29cf 122         request = self._makeRequest({'PATH_INFO': '/index.html'})
eac3ff 123         context = DummyContext()
CR 124         response = inst(context, request)
125         self.assertTrue(b'<html>static</html>' in response.body)
126
830864 127     def test_resource_is_file_with_wsgi_file_wrapper(self):
6b3cca 128         from pyramid.response import _BLOCK_SIZE
0c29cf 129
dd3cc8 130         inst = self._makeOne('tests:fixtures/static')
0c29cf 131         request = self._makeRequest({'PATH_INFO': '/index.html'})
MM 132
830864 133         class _Wrapper(object):
CM 134             def __init__(self, file, block_size=None):
135                 self.file = file
136                 self.block_size = block_size
0c29cf 137
830864 138         request.environ['wsgi.file_wrapper'] = _Wrapper
CM 139         context = DummyContext()
140         response = inst(context, request)
141         app_iter = response.app_iter
142         self.assertTrue(isinstance(app_iter, _Wrapper))
143         self.assertTrue(b'<html>static</html>' in app_iter.file.read())
144         self.assertEqual(app_iter.block_size, _BLOCK_SIZE)
3f7681 145         app_iter.file.close()
830864 146
d809ac 147     def test_resource_is_file_with_cache_max_age(self):
dd3cc8 148         inst = self._makeOne('tests:fixtures/static', cache_max_age=600)
0c29cf 149         request = self._makeRequest({'PATH_INFO': '/index.html'})
f2ef79 150         context = DummyContext()
CM 151         response = inst(context, request)
8e606d 152         self.assertTrue(b'<html>static</html>' in response.body)
f2ef79 153         self.assertEqual(len(response.headerlist), 5)
0c29cf 154         header_names = [x[0] for x in response.headerlist]
d809ac 155         header_names.sort()
0c29cf 156         self.assertEqual(
MM 157             header_names,
158             [
159                 'Cache-Control',
160                 'Content-Length',
161                 'Content-Type',
162                 'Expires',
163                 'Last-Modified',
164             ],
165         )
d809ac 166
CM 167     def test_resource_is_file_with_no_cache_max_age(self):
0c29cf 168         inst = self._makeOne('tests:fixtures/static', cache_max_age=None)
MM 169         request = self._makeRequest({'PATH_INFO': '/index.html'})
f2ef79 170         context = DummyContext()
CM 171         response = inst(context, request)
8e606d 172         self.assertTrue(b'<html>static</html>' in response.body)
fe1548 173         self.assertEqual(len(response.headerlist), 3)
0c29cf 174         header_names = [x[0] for x in response.headerlist]
d809ac 175         header_names.sort()
f2ef79 176         self.assertEqual(
0c29cf 177             header_names, ['Content-Length', 'Content-Type', 'Last-Modified']
MM 178         )
d809ac 179
f2ef79 180     def test_resource_notmodified(self):
dd3cc8 181         inst = self._makeOne('tests:fixtures/static')
0c29cf 182         request = self._makeRequest({'PATH_INFO': '/index.html'})
64aaf0 183         request.if_modified_since = fiveyrsfuture
f2ef79 184         context = DummyContext()
CM 185         response = inst(context, request)
c7446c 186         start_response = DummyStartResponse()
CM 187         app_iter = response(request.environ, start_response)
9f7de6 188         try:
CM 189             self.assertEqual(start_response.status, '304 Not Modified')
190             self.assertEqual(list(app_iter), [])
191         finally:
192             app_iter.close()
d809ac 193
CM 194     def test_not_found(self):
dd3cc8 195         inst = self._makeOne('tests:fixtures/static')
0c29cf 196         request = self._makeRequest({'PATH_INFO': '/notthere.html'})
f2ef79 197         context = DummyContext()
04af14 198         from pyramid.httpexceptions import HTTPNotFound
0c29cf 199
e7c4dd 200         self.assertRaises(HTTPNotFound, inst, context, request)
d809ac 201
ed06c6 202     def test_gz_resource_no_content_encoding(self):
dd3cc8 203         inst = self._makeOne('tests:fixtures/static')
0c29cf 204         request = self._makeRequest({'PATH_INFO': '/arcs.svg.tgz'})
0e5441 205         context = DummyContext()
CM 206         response = inst(context, request)
207         self.assertEqual(response.status, '200 OK')
208         self.assertEqual(response.content_type, 'application/x-tar')
ed06c6 209         self.assertEqual(response.content_encoding, None)
fee381 210         response.app_iter.close()
0e5441 211
CM 212     def test_resource_no_content_encoding(self):
dd3cc8 213         inst = self._makeOne('tests:fixtures/static')
0c29cf 214         request = self._makeRequest({'PATH_INFO': '/index.html'})
0e5441 215         context = DummyContext()
CM 216         response = inst(context, request)
217         self.assertEqual(response.status, '200 OK')
218         self.assertEqual(response.content_type, 'text/html')
219         self.assertEqual(response.content_encoding, None)
fee381 220         response.app_iter.close()
0e5441 221
0c29cf 222
f2ef79 223 class Test_static_view_use_subpath_True(unittest.TestCase):
b29429 224     def _getTargetClass(self):
56d0fe 225         from pyramid.static import static_view
0c29cf 226
56d0fe 227         return static_view
b29429 228
f2ef79 229     def _makeOne(self, *arg, **kw):
CM 230         kw['use_subpath'] = True
231         return self._getTargetClass()(*arg, **kw)
232
233     def _makeRequest(self, kw=None):
234         from pyramid.request import Request
0c29cf 235
b29429 236         environ = {
0c29cf 237             'wsgi.url_scheme': 'http',
MM 238             'wsgi.version': (1, 0),
239             'SERVER_NAME': 'example.com',
240             'SERVER_PORT': '6543',
241             'PATH_INFO': '/',
242             'SCRIPT_NAME': '',
243             'REQUEST_METHOD': 'GET',
244         }
f2ef79 245         if kw is not None:
CM 246             environ.update(kw)
247         return Request(environ=environ)
eac3ff 248
f2ef79 249     def test_ctor_defaultargs(self):
CM 250         inst = self._makeOne('package:resource_name')
251         self.assertEqual(inst.package_name, 'package')
252         self.assertEqual(inst.docroot, 'resource_name')
fe1548 253         self.assertEqual(inst.cache_max_age, 3600)
f2ef79 254         self.assertEqual(inst.index, 'index.html')
CM 255
256     def test_call_adds_slash_path_info_empty(self):
dd3cc8 257         inst = self._makeOne('tests:fixtures/static')
0c29cf 258         request = self._makeRequest({'PATH_INFO': ''})
f2ef79 259         request.subpath = ()
CM 260         context = DummyContext()
3d65af 261         from pyramid.httpexceptions import HTTPMovedPermanently
0c29cf 262
e7c4dd 263         self.assertRaises(HTTPMovedPermanently, inst, context, request)
3d65af 264
f2ef79 265     def test_path_info_slash_means_index_html(self):
dd3cc8 266         inst = self._makeOne('tests:fixtures/static')
f2ef79 267         request = self._makeRequest()
CM 268         request.subpath = ()
269         context = DummyContext()
270         response = inst(context, request)
8e606d 271         self.assertTrue(b'<html>static</html>' in response.body)
f2ef79 272
05b8a6 273     def test_oob_singledot(self):
dd3cc8 274         inst = self._makeOne('tests:fixtures/static')
05b8a6 275         request = self._makeRequest()
CM 276         request.subpath = ('.', 'index.html')
277         context = DummyContext()
04af14 278         from pyramid.httpexceptions import HTTPNotFound
0c29cf 279
e7c4dd 280         self.assertRaises(HTTPNotFound, inst, context, request)
05b8a6 281
CM 282     def test_oob_emptyelement(self):
dd3cc8 283         inst = self._makeOne('tests:fixtures/static')
05b8a6 284         request = self._makeRequest()
CM 285         request.subpath = ('', 'index.html')
286         context = DummyContext()
04af14 287         from pyramid.httpexceptions import HTTPNotFound
0c29cf 288
e7c4dd 289         self.assertRaises(HTTPNotFound, inst, context, request)
05b8a6 290
CM 291     def test_oob_dotdotslash(self):
dd3cc8 292         inst = self._makeOne('tests:fixtures/static')
f2ef79 293         request = self._makeRequest()
CM 294         request.subpath = ('subdir', '..', '..', 'minimal.pt')
295         context = DummyContext()
04af14 296         from pyramid.httpexceptions import HTTPNotFound
0c29cf 297
e7c4dd 298         self.assertRaises(HTTPNotFound, inst, context, request)
f2ef79 299
05b8a6 300     def test_oob_dotdotslash_encoded(self):
dd3cc8 301         inst = self._makeOne('tests:fixtures/static')
05b8a6 302         request = self._makeRequest()
CM 303         request.subpath = ('subdir', '%2E%2E', '%2E%2E', 'minimal.pt')
304         context = DummyContext()
04af14 305         from pyramid.httpexceptions import HTTPNotFound
0c29cf 306
e7c4dd 307         self.assertRaises(HTTPNotFound, inst, context, request)
eac3ff 308
05b8a6 309     def test_oob_os_sep(self):
CM 310         import os
0c29cf 311
dd3cc8 312         inst = self._makeOne('tests:fixtures/static')
05b8a6 313         dds = '..' + os.sep
CM 314         request = self._makeRequest()
315         request.subpath = ('subdir', dds, dds, 'minimal.pt')
316         context = DummyContext()
04af14 317         from pyramid.httpexceptions import HTTPNotFound
0c29cf 318
e7c4dd 319         self.assertRaises(HTTPNotFound, inst, context, request)
05b8a6 320
f2ef79 321     def test_resource_doesnt_exist(self):
dd3cc8 322         inst = self._makeOne('tests:fixtures/static')
f2ef79 323         request = self._makeRequest()
0c29cf 324         request.subpath = 'notthere,'
f2ef79 325         context = DummyContext()
04af14 326         from pyramid.httpexceptions import HTTPNotFound
0c29cf 327
e7c4dd 328         self.assertRaises(HTTPNotFound, inst, context, request)
f2ef79 329
CM 330     def test_resource_isdir(self):
dd3cc8 331         inst = self._makeOne('tests:fixtures/static')
f2ef79 332         request = self._makeRequest()
CM 333         request.subpath = ('subdir',)
334         context = DummyContext()
335         response = inst(context, request)
8e606d 336         self.assertTrue(b'<html>subdir</html>' in response.body)
f2ef79 337
CM 338     def test_resource_is_file(self):
dd3cc8 339         inst = self._makeOne('tests:fixtures/static')
f2ef79 340         request = self._makeRequest()
CM 341         request.subpath = ('index.html',)
342         context = DummyContext()
343         response = inst(context, request)
8e606d 344         self.assertTrue(b'<html>static</html>' in response.body)
f2ef79 345
CM 346     def test_resource_is_file_with_cache_max_age(self):
dd3cc8 347         inst = self._makeOne('tests:fixtures/static', cache_max_age=600)
f2ef79 348         request = self._makeRequest()
CM 349         request.subpath = ('index.html',)
350         context = DummyContext()
351         response = inst(context, request)
8e606d 352         self.assertTrue(b'<html>static</html>' in response.body)
f2ef79 353         self.assertEqual(len(response.headerlist), 5)
0c29cf 354         header_names = [x[0] for x in response.headerlist]
f2ef79 355         header_names.sort()
0c29cf 356         self.assertEqual(
MM 357             header_names,
358             [
359                 'Cache-Control',
360                 'Content-Length',
361                 'Content-Type',
362                 'Expires',
363                 'Last-Modified',
364             ],
365         )
f2ef79 366
CM 367     def test_resource_is_file_with_no_cache_max_age(self):
0c29cf 368         inst = self._makeOne('tests:fixtures/static', cache_max_age=None)
f2ef79 369         request = self._makeRequest()
CM 370         request.subpath = ('index.html',)
371         context = DummyContext()
372         response = inst(context, request)
8e606d 373         self.assertTrue(b'<html>static</html>' in response.body)
fe1548 374         self.assertEqual(len(response.headerlist), 3)
0c29cf 375         header_names = [x[0] for x in response.headerlist]
f2ef79 376         header_names.sort()
CM 377         self.assertEqual(
0c29cf 378             header_names, ['Content-Length', 'Content-Type', 'Last-Modified']
MM 379         )
f2ef79 380
CM 381     def test_resource_notmodified(self):
dd3cc8 382         inst = self._makeOne('tests:fixtures/static')
f2ef79 383         request = self._makeRequest()
64aaf0 384         request.if_modified_since = fiveyrsfuture
f2ef79 385         request.subpath = ('index.html',)
CM 386         context = DummyContext()
387         response = inst(context, request)
c7446c 388         start_response = DummyStartResponse()
CM 389         app_iter = response(request.environ, start_response)
9f7de6 390         try:
CM 391             self.assertEqual(start_response.status, '304 Not Modified')
392             self.assertEqual(list(app_iter), [])
393         finally:
394             app_iter.close()
f2ef79 395
CM 396     def test_not_found(self):
dd3cc8 397         inst = self._makeOne('tests:fixtures/static')
f2ef79 398         request = self._makeRequest()
CM 399         request.subpath = ('notthere.html',)
400         context = DummyContext()
04af14 401         from pyramid.httpexceptions import HTTPNotFound
0c29cf 402
e7c4dd 403         self.assertRaises(HTTPNotFound, inst, context, request)
f2ef79 404
f674a8 405
0c29cf 406 class TestQueryStringConstantCacheBuster(unittest.TestCase):
f674a8 407     def _makeOne(self, param=None):
CR 408         from pyramid.static import QueryStringConstantCacheBuster as cls
0c29cf 409
f674a8 410         if param:
CR 411             inst = cls('foo', param)
412         else:
413             inst = cls('foo')
414         return inst
415
416     def test_token(self):
780889 417         fut = self._makeOne().tokenize
4d19b8 418         self.assertEqual(fut(None, 'whatever', None), 'foo')
f674a8 419
5e3439 420     def test_it(self):
MM 421         fut = self._makeOne()
f674a8 422         self.assertEqual(
0c29cf 423             fut('foo', 'bar', {}), ('bar', {'_query': {'x': 'foo'}})
MM 424         )
f674a8 425
5e3439 426     def test_change_param(self):
MM 427         fut = self._makeOne('y')
f674a8 428         self.assertEqual(
0c29cf 429             fut('foo', 'bar', {}), ('bar', {'_query': {'y': 'foo'}})
MM 430         )
f674a8 431
5e3439 432     def test_query_is_already_tuples(self):
MM 433         fut = self._makeOne()
f674a8 434         self.assertEqual(
5e3439 435             fut('foo', 'bar', {'_query': [('a', 'b')]}),
0c29cf 436             ('bar', {'_query': (('a', 'b'), ('x', 'foo'))}),
MM 437         )
f674a8 438
5e3439 439     def test_query_is_tuple_of_tuples(self):
MM 440         fut = self._makeOne()
f674a8 441         self.assertEqual(
5e3439 442             fut('foo', 'bar', {'_query': (('a', 'b'),)}),
0c29cf 443             ('bar', {'_query': (('a', 'b'), ('x', 'foo'))}),
MM 444         )
445
891d05 446
3a4119 447 class TestManifestCacheBuster(unittest.TestCase):
MM 448     def _makeOne(self, path, **kw):
449         from pyramid.static import ManifestCacheBuster as cls
0c29cf 450
3a4119 451         return cls(path, **kw)
MM 452
453     def test_it(self):
454         manifest_path = os.path.join(here, 'fixtures', 'manifest.json')
5e3439 455         fut = self._makeOne(manifest_path)
MM 456         self.assertEqual(fut('foo', 'bar', {}), ('bar', {}))
3a4119 457         self.assertEqual(
0c29cf 458             fut('foo', 'css/main.css', {}), ('css/main-test.css', {})
MM 459         )
3a4119 460
1e1111 461     def test_it_with_relspec(self):
5e3439 462         fut = self._makeOne('fixtures/manifest.json')
MM 463         self.assertEqual(fut('foo', 'bar', {}), ('bar', {}))
1e1111 464         self.assertEqual(
0c29cf 465             fut('foo', 'css/main.css', {}), ('css/main-test.css', {})
MM 466         )
1e1111 467
MM 468     def test_it_with_absspec(self):
dd3cc8 469         fut = self._makeOne('tests:fixtures/manifest.json')
5e3439 470         self.assertEqual(fut('foo', 'bar', {}), ('bar', {}))
1e1111 471         self.assertEqual(
0c29cf 472             fut('foo', 'css/main.css', {}), ('css/main-test.css', {})
MM 473         )
1e1111 474
3a4119 475     def test_reload(self):
MM 476         manifest_path = os.path.join(here, 'fixtures', 'manifest.json')
477         new_manifest_path = os.path.join(here, 'fixtures', 'manifest2.json')
478         inst = self._makeOne('foo', reload=True)
479         inst.getmtime = lambda *args, **kwargs: 0
5e3439 480         fut = inst
3a4119 481
MM 482         # test without a valid manifest
0c29cf 483         self.assertEqual(fut('foo', 'css/main.css', {}), ('css/main.css', {}))
3a4119 484
MM 485         # swap to a real manifest, setting mtime to 0
486         inst.manifest_path = manifest_path
487         self.assertEqual(
0c29cf 488             fut('foo', 'css/main.css', {}), ('css/main-test.css', {})
MM 489         )
3a4119 490
MM 491         # ensure switching the path doesn't change the result
492         inst.manifest_path = new_manifest_path
493         self.assertEqual(
0c29cf 494             fut('foo', 'css/main.css', {}), ('css/main-test.css', {})
MM 495         )
3a4119 496
MM 497         # update mtime, should cause a reload
498         inst.getmtime = lambda *args, **kwargs: 1
499         self.assertEqual(
0c29cf 500             fut('foo', 'css/main.css', {}), ('css/main-678b7c80.css', {})
MM 501         )
3a4119 502
MM 503     def test_invalid_manifest(self):
504         self.assertRaises(IOError, lambda: self._makeOne('foo'))
505
506     def test_invalid_manifest_with_reload(self):
507         inst = self._makeOne('foo', reload=True)
508         self.assertEqual(inst.manifest, {})
509
0c29cf 510
b29429 511 class DummyContext:
CM 512     pass
0c29cf 513
b29429 514
c7446c 515 class DummyStartResponse:
CM 516     status = ()
517     headers = ()
0c29cf 518
c7446c 519     def __call__(self, status, headers):
CM 520         self.status = status
521         self.headers = headers