1414import base64
1515import ntpath
1616import shutil
17- import urllib .parse
17+ import email .message
18+ import email .utils
1819import html
1920import http .client
21+ import urllib .parse
2022import tempfile
2123import time
24+ import datetime
2225from io import BytesIO
2326
2427import unittest
@@ -333,6 +336,13 @@ def setUp(self):
333336 self .base_url = '/' + self .tempdir_name
334337 with open (os .path .join (self .tempdir , 'test' ), 'wb' ) as temp :
335338 temp .write (self .data )
339+ mtime = os .fstat (temp .fileno ()).st_mtime
340+ # compute last modification datetime for browser cache tests
341+ last_modif = datetime .datetime .fromtimestamp (mtime ,
342+ datetime .timezone .utc )
343+ self .last_modif_datetime = last_modif .replace (microsecond = 0 )
344+ self .last_modif_header = email .utils .formatdate (
345+ last_modif .timestamp (), usegmt = True )
336346
337347 def tearDown (self ):
338348 try :
@@ -444,6 +454,44 @@ def test_head(self):
444454 self .assertEqual (response .getheader ('content-type' ),
445455 'application/octet-stream' )
446456
457+ def test_browser_cache (self ):
458+ """Check that when a request to /test is sent with the request header
459+ If-Modified-Since set to date of last modification, the server returns
460+ status code 304, not 200
461+ """
462+ headers = email .message .Message ()
463+ headers ['If-Modified-Since' ] = self .last_modif_header
464+ response = self .request (self .base_url + '/test' , headers = headers )
465+ self .check_status_and_reason (response , HTTPStatus .NOT_MODIFIED )
466+
467+ # one hour after last modification : must return 304
468+ new_dt = self .last_modif_datetime + datetime .timedelta (hours = 1 )
469+ headers = email .message .Message ()
470+ headers ['If-Modified-Since' ] = email .utils .format_datetime (new_dt ,
471+ usegmt = True )
472+ response = self .request (self .base_url + '/test' , headers = headers )
473+ self .check_status_and_reason (response , HTTPStatus .NOT_MODIFIED )
474+
475+ def test_browser_cache_file_changed (self ):
476+ # with If-Modified-Since earlier than Last-Modified, must return 200
477+ dt = self .last_modif_datetime
478+ # build datetime object : 365 days before last modification
479+ old_dt = dt - datetime .timedelta (days = 365 )
480+ headers = email .message .Message ()
481+ headers ['If-Modified-Since' ] = email .utils .format_datetime (old_dt ,
482+ usegmt = True )
483+ response = self .request (self .base_url + '/test' , headers = headers )
484+ self .check_status_and_reason (response , HTTPStatus .OK )
485+
486+ def test_browser_cache_with_If_None_Match_header (self ):
487+ # if If-None-Match header is present, ignore If-Modified-Since
488+
489+ headers = email .message .Message ()
490+ headers ['If-Modified-Since' ] = self .last_modif_header
491+ headers ['If-None-Match' ] = "*"
492+ response = self .request (self .base_url + '/test' , headers = headers )
493+ self .check_status_and_reason (response , HTTPStatus .OK )
494+
447495 def test_invalid_requests (self ):
448496 response = self .request ('/' , method = 'FOO' )
449497 self .check_status_and_reason (response , HTTPStatus .NOT_IMPLEMENTED )
@@ -453,6 +501,15 @@ def test_invalid_requests(self):
453501 response = self .request ('/' , method = 'GETs' )
454502 self .check_status_and_reason (response , HTTPStatus .NOT_IMPLEMENTED )
455503
504+ def test_last_modified (self ):
505+ """Checks that the datetime returned in Last-Modified response header
506+ is the actual datetime of last modification, rounded to the second
507+ """
508+ response = self .request (self .base_url + '/test' )
509+ self .check_status_and_reason (response , HTTPStatus .OK , data = self .data )
510+ last_modif_header = response .headers ['Last-modified' ]
511+ self .assertEqual (last_modif_header , self .last_modif_header )
512+
456513 def test_path_without_leading_slash (self ):
457514 response = self .request (self .tempdir_name + '/test' )
458515 self .check_status_and_reason (response , HTTPStatus .OK , data = self .data )
0 commit comments