App Engine Java SDK version 1.9.14
[gae.git] / python / google / appengine / tools / devappserver2 / static_files_handler_test.py
blob3a68c894e4fe6dd51c01dd07978c111797e7e512
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Tests for google.appengine.tools.devappserver2.static_files_handler."""
20 import errno
21 import os.path
22 import unittest
24 import google
25 import mox
27 from google.appengine.api import appinfo
28 from google.appengine.tools.devappserver2 import errors
29 from google.appengine.tools.devappserver2 import static_files_handler
30 from google.appengine.tools.devappserver2 import wsgi_test_utils
33 class TestStaticContentHandlerHandlePath(wsgi_test_utils.WSGITestCase):
34 """Tests for static_files_handler.StaticContentHandler._handle_path."""
36 def setUp(self):
37 self.mox = mox.Mox()
38 self.mox.StubOutWithMock(os.path, 'getmtime')
39 self.mox.StubOutWithMock(static_files_handler.StaticContentHandler,
40 '_read_file')
42 def tearDown(self):
43 static_files_handler.StaticContentHandler._filename_to_mtime_and_etag = {}
44 self.mox.UnsetStubs()
46 def test_load_file(self):
47 url_map = appinfo.URLMap(url='/',
48 static_files='index.html')
50 h = static_files_handler.StaticContentHandler(
51 root_path=None,
52 url_map=url_map,
53 url_pattern='/$')
55 os.path.getmtime('/home/appdir/index.html').AndReturn(12345.6)
56 static_files_handler.StaticContentHandler._read_file(
57 '/home/appdir/index.html').AndReturn('Hello World!')
59 self.mox.ReplayAll()
60 self.assertResponse('200 OK',
61 {'Content-type': 'text/html',
62 'Content-length': '12',
63 'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT',
64 'Cache-Control': 'no-cache',
65 'ETag': '"NDcyNDU2MzU1"'},
66 'Hello World!',
67 h._handle_path,
68 '/home/appdir/index.html',
69 {'REQUEST_METHOD': 'GET'})
70 self.mox.VerifyAll()
71 self.assertEqual(
72 static_files_handler.StaticContentHandler._filename_to_mtime_and_etag,
73 {'/home/appdir/index.html': (12345.6, 'NDcyNDU2MzU1')})
75 def test_load_cached_file(self):
76 static_files_handler.StaticContentHandler._filename_to_mtime_and_etag = {
77 '/home/appdir/index.html': (12345.6, 'NDcyNDU2MzU1')}
79 url_map = appinfo.URLMap(url='/',
80 static_files='index.html')
82 h = static_files_handler.StaticContentHandler(
83 root_path=None,
84 url_map=url_map,
85 url_pattern='/$')
87 os.path.getmtime('/home/appdir/index.html').AndReturn(12345.6)
88 static_files_handler.StaticContentHandler._read_file(
89 '/home/appdir/index.html').AndReturn('Hello World!')
91 self.mox.ReplayAll()
92 self.assertResponse('200 OK',
93 {'Content-type': 'text/html',
94 'Content-length': '12',
95 'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT',
96 'Cache-Control': 'no-cache',
97 'ETag': '"NDcyNDU2MzU1"'},
98 'Hello World!',
99 h._handle_path,
100 '/home/appdir/index.html',
101 {'REQUEST_METHOD': 'GET'})
102 self.mox.VerifyAll()
103 self.assertEqual(
104 static_files_handler.StaticContentHandler._filename_to_mtime_and_etag,
105 {'/home/appdir/index.html': (12345.6, 'NDcyNDU2MzU1')})
107 def test_load_head(self):
108 url_map = appinfo.URLMap(url='/',
109 static_files='index.html')
111 h = static_files_handler.StaticContentHandler(
112 root_path=None,
113 url_map=url_map,
114 url_pattern='/$')
116 os.path.getmtime('/home/appdir/index.html').AndReturn(12345.6)
117 static_files_handler.StaticContentHandler._read_file(
118 '/home/appdir/index.html').AndReturn('Hello World!')
120 self.mox.ReplayAll()
121 self.assertResponse('200 OK',
122 {'Content-type': 'text/html',
123 'Content-length': '12',
124 'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT',
125 'Cache-Control': 'no-cache',
126 'ETag': '"NDcyNDU2MzU1"'},
128 h._handle_path,
129 '/home/appdir/index.html',
130 {'REQUEST_METHOD': 'HEAD'})
131 self.mox.VerifyAll()
132 self.assertEqual(
133 static_files_handler.StaticContentHandler._filename_to_mtime_and_etag,
134 {'/home/appdir/index.html': (12345.6, 'NDcyNDU2MzU1')})
136 def test_no_permission_read(self):
137 url_map = appinfo.URLMap(url='/',
138 static_files='index.html')
140 h = static_files_handler.StaticContentHandler(
141 root_path=None,
142 url_map=url_map,
143 url_pattern='/$')
145 os.path.getmtime('/home/appdir/index.html').AndReturn(12345.6)
146 error = IOError()
147 error.errno = errno.EPERM
148 static_files_handler.StaticContentHandler._read_file(
149 '/home/appdir/index.html').AndRaise(error)
151 self.mox.ReplayAll()
152 self.assertResponse('403 Forbidden',
155 h._handle_path,
156 '/home/appdir/index.html',
157 {'REQUEST_METHOD': 'GET',
158 'PATH_INFO': '/'})
159 self.mox.VerifyAll()
161 def test_cached_no_permission_read(self):
162 static_files_handler.StaticContentHandler._filename_to_mtime_and_etag = {
163 '/home/appdir/index.html': (12345.6, 'NDcyNDU2MzU1')}
165 url_map = appinfo.URLMap(url='/',
166 static_files='index.html')
168 h = static_files_handler.StaticContentHandler(
169 root_path=None,
170 url_map=url_map,
171 url_pattern='/$')
173 os.path.getmtime('/home/appdir/index.html').AndReturn(12345.6)
174 error = IOError()
175 error.errno = errno.EPERM
176 static_files_handler.StaticContentHandler._read_file(
177 '/home/appdir/index.html').AndRaise(error)
179 self.mox.ReplayAll()
180 self.assertResponse('403 Forbidden',
183 h._handle_path,
184 '/home/appdir/index.html',
185 {'REQUEST_METHOD': 'GET',
186 'PATH_INFO': '/'})
187 self.mox.VerifyAll()
189 def test_file_does_not_exist_read(self):
190 url_map = appinfo.URLMap(url='/',
191 static_files='index.html')
193 h = static_files_handler.StaticContentHandler(
194 root_path=None,
195 url_map=url_map,
196 url_pattern='/$')
198 os.path.getmtime('/home/appdir/index.html').AndReturn(12345.6)
199 error = IOError()
200 error.errno = errno.ENOENT
201 static_files_handler.StaticContentHandler._read_file(
202 '/home/appdir/index.html').AndRaise(error)
204 self.mox.ReplayAll()
205 self.assertResponse('404 Not Found',
208 h._handle_path,
209 '/home/appdir/index.html',
210 {'REQUEST_METHOD': 'GET',
211 'PATH_INFO': '/'})
212 self.mox.VerifyAll()
214 def test_file_does_not_exist_stat(self):
215 url_map = appinfo.URLMap(url='/',
216 static_files='index.html')
218 h = static_files_handler.StaticContentHandler(
219 root_path=None,
220 url_map=url_map,
221 url_pattern='/$')
223 error = IOError()
224 error.errno = errno.ENOENT
225 os.path.getmtime('/home/appdir/index.html').AndRaise(error)
227 self.mox.ReplayAll()
228 self.assertResponse('404 Not Found',
231 h._handle_path,
232 '/home/appdir/index.html',
233 {'REQUEST_METHOD': 'GET',
234 'PATH_INFO': '/'})
235 self.mox.VerifyAll()
237 def test_if_match_without_match(self):
238 url_map = appinfo.URLMap(url='/',
239 static_files='index.html')
241 h = static_files_handler.StaticContentHandler(
242 root_path=None,
243 url_map=url_map,
244 url_pattern='/$')
246 os.path.getmtime('/home/appdir/index.html').AndReturn(12345.6)
247 static_files_handler.StaticContentHandler._read_file(
248 '/home/appdir/index.html').AndReturn('Hello World!')
250 self.mox.ReplayAll()
251 self.assertResponse('412 Precondition Failed',
252 {'ETag': '"NDcyNDU2MzU1"'},
254 h._handle_path,
255 '/home/appdir/index.html',
256 {'REQUEST_METHOD': 'GET',
257 'HTTP_IF_MATCH': '"nomatch"'})
258 self.mox.VerifyAll()
259 self.assertEqual(
260 static_files_handler.StaticContentHandler._filename_to_mtime_and_etag,
261 {'/home/appdir/index.html': (12345.6, 'NDcyNDU2MzU1')})
263 def test_if_match_no_file(self):
264 url_map = appinfo.URLMap(url='/',
265 static_files='index.html')
267 h = static_files_handler.StaticContentHandler(
268 root_path=None,
269 url_map=url_map,
270 url_pattern='/$')
272 error = IOError()
273 error.errno = errno.ENOENT
274 os.path.getmtime('/home/appdir/index.html').AndRaise(error)
276 self.mox.ReplayAll()
277 self.assertResponse('412 Precondition Failed',
280 h._handle_path,
281 '/home/appdir/index.html',
282 {'REQUEST_METHOD': 'GET',
283 'HTTP_IF_MATCH': '"nomatch"'})
284 self.mox.VerifyAll()
285 self.assertEqual(
286 static_files_handler.StaticContentHandler._filename_to_mtime_and_etag,
289 def test_cached_if_match_without_match(self):
290 static_files_handler.StaticContentHandler._filename_to_mtime_and_etag = {
291 '/home/appdir/index.html': (12345.6, 'abc')}
293 url_map = appinfo.URLMap(url='/',
294 static_files='index.html')
296 h = static_files_handler.StaticContentHandler(
297 root_path=None,
298 url_map=url_map,
299 url_pattern='/$')
301 os.path.getmtime('/home/appdir/index.html').AndReturn(12345.6)
303 self.mox.ReplayAll()
304 self.assertResponse('412 Precondition Failed',
305 {'ETag': '"abc"'},
307 h._handle_path,
308 '/home/appdir/index.html',
309 {'REQUEST_METHOD': 'GET',
310 'HTTP_IF_MATCH': '"nomatch"'})
311 self.mox.VerifyAll()
312 self.assertEqual(
313 static_files_handler.StaticContentHandler._filename_to_mtime_and_etag,
314 {'/home/appdir/index.html': (12345.6, 'abc')})
316 def test_if_none_match_with_match(self):
317 url_map = appinfo.URLMap(url='/',
318 static_files='index.html')
320 h = static_files_handler.StaticContentHandler(
321 root_path=None,
322 url_map=url_map,
323 url_pattern='/$')
325 os.path.getmtime('/home/appdir/index.html').AndReturn(12345.6)
326 static_files_handler.StaticContentHandler._read_file(
327 '/home/appdir/index.html').AndReturn('Hello World!')
329 self.mox.ReplayAll()
330 self.assertResponse('304 Not Modified',
331 {'ETag': '"NDcyNDU2MzU1"'},
333 h._handle_path,
334 '/home/appdir/index.html',
335 {'REQUEST_METHOD': 'GET',
336 'HTTP_IF_NONE_MATCH': '"NDcyNDU2MzU1"'})
337 self.mox.VerifyAll()
338 self.assertEqual(
339 static_files_handler.StaticContentHandler._filename_to_mtime_and_etag,
340 {'/home/appdir/index.html': (12345.6, 'NDcyNDU2MzU1')})
342 def test_cached_if_none_match_with_match(self):
343 static_files_handler.StaticContentHandler._filename_to_mtime_and_etag = {
344 '/home/appdir/index.html': (12345.6, 'match')}
346 url_map = appinfo.URLMap(url='/',
347 static_files='index.html')
349 h = static_files_handler.StaticContentHandler(
350 root_path=None,
351 url_map=url_map,
352 url_pattern='/$')
354 os.path.getmtime('/home/appdir/index.html').AndReturn(12345.6)
356 self.mox.ReplayAll()
357 self.assertResponse('304 Not Modified',
358 {'ETag': '"match"'},
360 h._handle_path,
361 '/home/appdir/index.html',
362 {'REQUEST_METHOD': 'GET',
363 'HTTP_IF_NONE_MATCH': '"match"'})
364 self.mox.VerifyAll()
365 self.assertEqual(
366 static_files_handler.StaticContentHandler._filename_to_mtime_and_etag,
367 {'/home/appdir/index.html': (12345.6, 'match')})
369 def test_custom_headers(self):
370 http_headers = appinfo.HttpHeadersDict()
371 http_headers['Content-type'] = 'text/xml'
372 http_headers['ETag'] = 'abc123'
373 http_headers['Expires'] = 'tomorrow'
374 http_headers['Cache-Control'] = 'private'
375 http_headers['Custom-Header'] = 'custom,value'
377 url_map = appinfo.URLMap(url='/',
378 static_files='index.html',
379 http_headers=http_headers)
381 h = static_files_handler.StaticContentHandler(
382 root_path=None,
383 url_map=url_map,
384 url_pattern='/$')
386 os.path.getmtime('/home/appdir/index.html').AndReturn(12345.6)
387 static_files_handler.StaticContentHandler._read_file(
388 '/home/appdir/index.html').AndReturn('Hello World!')
390 self.mox.ReplayAll()
391 self.assertResponse('200 OK',
392 {'Content-length': '12',
393 'Content-type': 'text/xml',
394 'ETag': 'abc123',
395 'Expires': 'tomorrow',
396 'Cache-Control': 'private',
397 'Custom-Header': 'custom,value'},
398 'Hello World!',
399 h._handle_path,
400 '/home/appdir/index.html',
401 {'REQUEST_METHOD': 'GET'})
402 self.mox.VerifyAll()
403 self.assertEqual(
404 static_files_handler.StaticContentHandler._filename_to_mtime_and_etag,
405 {'/home/appdir/index.html': (12345.6, 'NDcyNDU2MzU1')})
407 def test_custom_mimetype(self):
408 url_map = appinfo.URLMap(url='/',
409 mime_type='text/xml',
410 static_files='index.html')
412 h = static_files_handler.StaticContentHandler(
413 root_path=None,
414 url_map=url_map,
415 url_pattern='/$')
417 os.path.getmtime('/home/appdir/index.html').AndReturn(12345.6)
418 static_files_handler.StaticContentHandler._read_file(
419 '/home/appdir/index.html').AndReturn('Hello World!')
421 self.mox.ReplayAll()
422 self.assertResponse('200 OK',
423 {'Content-type': 'text/xml',
424 'Content-length': '12',
425 'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT',
426 'Cache-Control': 'no-cache',
427 'ETag': '"NDcyNDU2MzU1"'},
428 'Hello World!',
429 h._handle_path,
430 '/home/appdir/index.html',
431 {'REQUEST_METHOD': 'GET'})
432 self.mox.VerifyAll()
433 self.assertEqual(
434 static_files_handler.StaticContentHandler._filename_to_mtime_and_etag,
435 {'/home/appdir/index.html': (12345.6, 'NDcyNDU2MzU1')})
437 def test_custom_expiration_ignored(self):
438 url_map = appinfo.URLMap(url='/',
439 expiration='1d 2h 3m 4s',
440 static_files='index.html')
442 h = static_files_handler.StaticContentHandler(
443 root_path=None,
444 url_map=url_map,
445 url_pattern='/$')
447 os.path.getmtime('/home/appdir/index.html').AndReturn(12345.6)
448 static_files_handler.StaticContentHandler._read_file(
449 '/home/appdir/index.html').AndReturn('Hello World!')
451 self.mox.ReplayAll()
452 self.assertResponse('200 OK',
453 {'Content-type': 'text/html',
454 'Content-length': '12',
455 'ETag': '"NDcyNDU2MzU1"',
456 'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT',
457 'Cache-Control': 'no-cache'},
458 'Hello World!',
459 h._handle_path,
460 '/home/appdir/index.html',
461 {'REQUEST_METHOD': 'GET'})
462 self.mox.VerifyAll()
463 self.assertEqual(
464 static_files_handler.StaticContentHandler._filename_to_mtime_and_etag,
465 {'/home/appdir/index.html': (12345.6, 'NDcyNDU2MzU1')})
467 def test_nonstandard_mimetype(self):
468 url_map = appinfo.URLMap(url='/',
469 static_files='simple.dart')
471 h = static_files_handler.StaticContentHandler(
472 root_path=None,
473 url_map=url_map,
474 url_pattern='/$')
476 os.path.getmtime('/home/appdir/simple.dart').AndReturn(12345.6)
477 static_files_handler.StaticContentHandler._read_file(
478 '/home/appdir/simple.dart').AndReturn('void main() {}')
480 self.mox.ReplayAll()
481 self.assertResponse('200 OK',
482 {'Content-type': 'application/dart',
483 'Content-length': '14',
484 'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT',
485 'Cache-Control': 'no-cache',
486 'ETag': '"LTE2OTA2MzYyMTM="'},
487 'void main() {}',
488 h._handle_path,
489 '/home/appdir/simple.dart',
490 {'REQUEST_METHOD': 'GET'})
491 self.mox.VerifyAll()
494 class TestStaticContentHandlerCheckEtagMatch(unittest.TestCase):
495 """Tests for static_files_handler.StaticContentHandler._check_etag_match."""
497 def test_strong_match_required(self):
498 self.assertTrue(
499 static_files_handler.StaticContentHandler._check_etag_match(
500 '"abc"', 'abc', allow_weak_match=False))
502 def test_strong_match_no_match(self):
503 self.assertFalse(
504 static_files_handler.StaticContentHandler._check_etag_match(
505 '"nomatch"', 'abc', allow_weak_match=False))
507 def test_strong_match_with_weak_tag(self):
508 self.assertFalse(
509 static_files_handler.StaticContentHandler._check_etag_match(
510 'W/"abc"', 'abc', allow_weak_match=False))
512 def test_strong_match_star(self):
513 self.assertTrue(
514 static_files_handler.StaticContentHandler._check_etag_match(
515 '*', 'abc', allow_weak_match=False))
517 def test_weak_match_required(self):
518 self.assertTrue(
519 static_files_handler.StaticContentHandler._check_etag_match(
520 '"abc"', 'abc', allow_weak_match=True))
522 def test_weak_match_no_match(self):
523 self.assertFalse(
524 static_files_handler.StaticContentHandler._check_etag_match(
525 '"nomatch"', 'abc', allow_weak_match=True))
527 def test_weak_match_with_weak_tag(self):
528 self.assertTrue(
529 static_files_handler.StaticContentHandler._check_etag_match(
530 'W/"abc"', 'abc', allow_weak_match=True))
532 def test_weak_match_star(self):
533 self.assertTrue(
534 static_files_handler.StaticContentHandler._check_etag_match(
535 '*', 'abc', allow_weak_match=True))
537 def test_many_etags_match(self):
538 self.assertTrue(
539 static_files_handler.StaticContentHandler._check_etag_match(
540 '"abc", "def", "ghi"', 'def', allow_weak_match=False))
542 def test_many_etags_no_match(self):
543 self.assertFalse(
544 static_files_handler.StaticContentHandler._check_etag_match(
545 '"abc", "def", "ghi"', 'jkl', allow_weak_match=False))
548 class TestStaticFilesHandler(wsgi_test_utils.WSGITestCase):
549 """Tests for static_files_handler.StaticFilesHandler."""
551 def setUp(self):
552 self.mox = mox.Mox()
553 self.mox.StubOutWithMock(static_files_handler.StaticContentHandler,
554 '_handle_path')
556 def tearDown(self):
557 self.mox.UnsetStubs()
559 def test_simple_path(self):
560 url_map = appinfo.URLMap(url='/',
561 static_files='index.html')
562 h = static_files_handler.StaticFilesHandler(root_path='/appdir',
563 url_map=url_map)
564 match = h.match('/')
565 self.assertTrue(match)
566 self.assertFalse(h.match('/other'))
568 static_files_handler.StaticContentHandler._handle_path(
569 os.path.join('/appdir', 'index.html'),
571 mox.IgnoreArg()).AndReturn('<output>')
572 self.mox.ReplayAll()
573 self.assertEqual('<output>',
574 h.handle(match, {}, None))
575 self.mox.VerifyAll()
577 def test_patterned_path(self):
578 url_map = appinfo.URLMap(url=r'/(.*)/(.*)',
579 static_files=r'static/\1/subdir/\2')
580 h = static_files_handler.StaticFilesHandler(root_path='/appdir',
581 url_map=url_map)
582 match = h.match('/hello/foo.jpg')
583 self.assertTrue(match)
584 self.assertFalse(h.match('/'))
586 static_files_handler.StaticContentHandler._handle_path(
587 os.path.join('/appdir', 'static/hello/subdir/foo.jpg'),
589 mox.IgnoreArg()).AndReturn('<output>')
590 self.mox.ReplayAll()
591 self.assertEqual('<output>',
592 h.handle(match, {}, None))
593 self.mox.VerifyAll()
595 def test_invalid_regex(self):
596 url_map = appinfo.URLMap(url='((.*))(',
597 static_files='index.html')
598 self.assertRaises(errors.InvalidAppConfigError,
599 static_files_handler.StaticFilesHandler,
600 root_path='/appdir',
601 url_map=url_map)
604 class TestStaticDirHandler(wsgi_test_utils.WSGITestCase):
605 """Tests for static_files_handler.StaticDirHandler."""
607 def setUp(self):
608 self.mox = mox.Mox()
609 self.mox.StubOutWithMock(static_files_handler.StaticContentHandler,
610 '_handle_path')
612 def tearDown(self):
613 self.mox.UnsetStubs()
615 def test_simple_path(self):
616 url_map = appinfo.URLMap(url='/foo',
617 static_dir='subdir')
618 h = static_files_handler.StaticDirHandler(root_path='/appdir',
619 url_map=url_map)
620 match = h.match('/foo/bar.jpg')
621 self.assertTrue(match)
622 self.assertFalse(h.match('/baz'))
624 static_files_handler.StaticContentHandler._handle_path(
625 os.path.join('/appdir', 'subdir', 'bar.jpg'),
627 mox.IgnoreArg()).AndReturn('<output>')
628 self.mox.ReplayAll()
629 self.assertEqual('<output>',
630 h.handle(match, {}, None))
631 self.mox.VerifyAll()
633 def test_invalid_regex(self):
634 url_map = appinfo.URLMap(url='((.*))(',
635 static_files='index.html')
636 self.assertRaises(errors.InvalidAppConfigError,
637 static_files_handler.StaticDirHandler,
638 root_path='/appdir',
639 url_map=url_map)
641 if __name__ == '__main__':
642 unittest.main()