[web/httpcache] fix Last-Modified generation
authorJulien Cristau <julien.cristau@logilab.fr>
Wed, 09 Mar 2016 14:53:33 +0100
changeset 11241 a2091fa8cb2c
parent 11240 1694e6e9ff94
child 11242 b05c7a96d621
[web/httpcache] fix Last-Modified generation time.mktime takes a time tuple in *local* time. We have an UTC datetime coming from the last_modified method, which we were interpreting as local time, leading to hilarity. Use calendar.timegm instead. Add tests for Last-Modified generation of the 'download' and 'manage' views.
cubicweb/web/httpcache.py
cubicweb/web/test/unittest_idownloadable.py
cubicweb/web/test/unittest_views_basecontrollers.py
--- a/cubicweb/web/httpcache.py	Wed May 04 17:07:41 2016 +0200
+++ b/cubicweb/web/httpcache.py	Wed Mar 09 14:53:33 2016 +0100
@@ -19,7 +19,7 @@
 
 __docformat__ = "restructuredtext en"
 
-from time import mktime
+from calendar import timegm
 from datetime import datetime
 
 class NoHTTPCacheManager(object):
@@ -78,7 +78,7 @@
         # the front-end correctly generate it
         # ("%a, %d %b %Y %H:%M:%S GMT" return localized date that
         # twisted don't parse correctly)
-        req.set_header('Last-modified', mktime(mdate.timetuple()), raw=False)
+        req.set_header('Last-modified', timegm(mdate.timetuple()), raw=False)
 
 
 class EntityHTTPCacheManager(EtagHTTPCacheManager):
--- a/cubicweb/web/test/unittest_idownloadable.py	Wed May 04 17:07:41 2016 +0200
+++ b/cubicweb/web/test/unittest_idownloadable.py	Wed Mar 09 14:53:33 2016 +0100
@@ -17,13 +17,16 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 
+from datetime import datetime
 from functools import partial
 
-from logilab.common.testlib import unittest_main
+from pytz import utc
 
 from cubicweb.devtools.testlib import CubicWebTC, real_error_handling
 from cubicweb import view
 from cubicweb.predicates import is_instance
+from cubicweb.web import http_headers
+
 
 class IDownloadableUser(view.EntityAdapter):
     __regid__ = 'IDownloadable'
@@ -72,6 +75,9 @@
                              get('content-disposition'))
             self.assertEqual(['text/plain;charset=ascii'],
                              get('content-type'))
+            last_mod = http_headers.parseDateTime(get('last-modified')[0])
+            self.assertEqual(datetime.fromtimestamp(last_mod, tz=utc),
+                             req.user.modification_date.replace(microsecond=0))
             self.assertEqual(b'Babar is not dead!', data)
 
     def test_header_with_space(self):
@@ -146,4 +152,5 @@
             self.assertEqual(req.status_out, 500)
 
 if __name__ == '__main__':
-    unittest_main()
+    from unittest import main
+    main()
--- a/cubicweb/web/test/unittest_views_basecontrollers.py	Wed May 04 17:07:41 2016 +0200
+++ b/cubicweb/web/test/unittest_views_basecontrollers.py	Wed Mar 09 14:53:33 2016 +0100
@@ -17,6 +17,8 @@
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
 """cubicweb.web.views.basecontrollers unit tests"""
 
+import time
+
 from six import text_type
 from six.moves.urllib.parse import urlsplit, urlunsplit, urljoin, parse_qs
 
@@ -31,7 +33,7 @@
 from cubicweb.devtools.webtest import CubicWebTestTC
 from cubicweb.utils import json_dumps
 from cubicweb.uilib import rql_for_eid
-from cubicweb.web import Redirect, RemoteCallFailed
+from cubicweb.web import Redirect, RemoteCallFailed, http_headers
 import cubicweb.server.session
 from cubicweb.server.session import Connection
 from cubicweb.web.views.autoform import get_pending_inserts, get_pending_deletes
@@ -44,9 +46,11 @@
 
 class ViewControllerTC(CubicWebTestTC):
     def test_view_ctrl_with_valid_cache_headers(self):
+        now = time.time()
         resp = self.webapp.get('/manage')
         self.assertEqual(resp.etag, 'manage/guests')
         self.assertEqual(resp.status_code, 200)
+        self.assertGreaterEqual(http_headers.parseDateTime(resp.headers['Last-Modified']), int(now))
         cache_headers = {'if-modified-since': resp.headers['Last-Modified'],
                          'if-none-match': resp.etag}
         resp = self.webapp.get('/manage', headers=cache_headers)