cubicweb/web/test/unittest_http.py
changeset 11057 0b59724cb3f2
parent 10849 79066409fdcf
child 11174 eb88fdfd3740
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
       
     1 # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 
       
    19 import contextlib
       
    20 
       
    21 from logilab.common.testlib import TestCase, unittest_main, tag, Tags
       
    22 
       
    23 from cubicweb.devtools.fake import FakeRequest
       
    24 from cubicweb.devtools.testlib import CubicWebTC
       
    25 
       
    26 
       
    27 def _test_cache(hin, hout, method='GET'):
       
    28     """forge and process an HTTP request using given headers in/out and method,
       
    29     then return it once its .is_client_cache_valid() method has been called.
       
    30 
       
    31     req.status_out is None if the page should have been calculated.
       
    32     """
       
    33     # forge request
       
    34     req = FakeRequest(method=method)
       
    35     for key, value in hin:
       
    36         req._headers_in.addRawHeader(key, str(value))
       
    37     for key, value in hout:
       
    38         req.headers_out.addRawHeader(key, str(value))
       
    39     # process
       
    40     req.status_out = None
       
    41     req.is_client_cache_valid()
       
    42     return req
       
    43 
       
    44 class HTTPCache(TestCase):
       
    45     """Check that the http cache logiac work as expected
       
    46     (as far as we understood the RFC)
       
    47 
       
    48     """
       
    49     tags = TestCase.tags | Tags('http', 'cache')
       
    50 
       
    51 
       
    52     def assertCache(self, expected, status, situation=''):
       
    53         """simple assert for nicer message"""
       
    54         if expected != status:
       
    55             if expected is None:
       
    56                 expected = "MODIFIED"
       
    57             if status is None:
       
    58                 status = "MODIFIED"
       
    59             msg = 'expected %r got %r' % (expected, status)
       
    60             if situation:
       
    61                 msg = "%s - when: %s" % (msg, situation)
       
    62             self.fail(msg)
       
    63 
       
    64     def test_IN_none_OUT_none(self):
       
    65         #: test that no caching is requested when not data is available
       
    66         #: on any side
       
    67         req =_test_cache((), ())
       
    68         self.assertIsNone(req.status_out)
       
    69 
       
    70     def test_IN_Some_OUT_none(self):
       
    71         #: test that no caching is requested when no data is available
       
    72         #: server (origin) side
       
    73         hin = [('if-modified-since','Sat, 14 Apr 2012 14:39:32 GM'),
       
    74               ]
       
    75         req = _test_cache(hin, ())
       
    76         self.assertIsNone(req.status_out)
       
    77         hin = [('if-none-match','babar/huitre'),
       
    78               ]
       
    79         req = _test_cache(hin, ())
       
    80         self.assertIsNone(req.status_out)
       
    81         hin = [('if-modified-since','Sat, 14 Apr 2012 14:39:32 GM'),
       
    82                ('if-none-match','babar/huitre'),
       
    83               ]
       
    84         req = _test_cache(hin, ())
       
    85         self.assertIsNone(req.status_out)
       
    86 
       
    87     def test_IN_none_OUT_Some(self):
       
    88         #: test that no caching is requested when no data is provided
       
    89         #: by the client
       
    90         hout = [('last-modified','Sat, 14 Apr 2012 14:39:32 GM'),
       
    91                ]
       
    92         req = _test_cache((), hout)
       
    93         self.assertIsNone(req.status_out)
       
    94         hout = [('etag','babar/huitre'),
       
    95                ]
       
    96         req = _test_cache((), hout)
       
    97         self.assertIsNone(req.status_out)
       
    98         hout = [('last-modified', 'Sat, 14 Apr 2012 14:39:32 GM'),
       
    99                 ('etag','babar/huitre'),
       
   100                ]
       
   101         req = _test_cache((), hout)
       
   102         self.assertIsNone(req.status_out)
       
   103 
       
   104     @tag('last_modified')
       
   105     def test_last_modified_newer(self):
       
   106         #: test the proper behavior of modification date only
       
   107         # newer
       
   108         hin  = [('if-modified-since', 'Sat, 13 Apr 2012 14:39:32 GM'),
       
   109                ]
       
   110         hout = [('last-modified', 'Sat, 14 Apr 2012 14:39:32 GM'),
       
   111                ]
       
   112         req = _test_cache(hin, hout)
       
   113         self.assertCache(None, req.status_out, 'origin is newer than client')
       
   114 
       
   115     @tag('last_modified')
       
   116     def test_last_modified_older(self):
       
   117         # older
       
   118         hin  = [('if-modified-since', 'Sat, 15 Apr 2012 14:39:32 GM'),
       
   119                ]
       
   120         hout = [('last-modified', 'Sat, 14 Apr 2012 14:39:32 GM'),
       
   121                ]
       
   122         req = _test_cache(hin, hout)
       
   123         self.assertCache(304, req.status_out, 'origin is older than client')
       
   124 
       
   125     @tag('last_modified')
       
   126     def test_last_modified_same(self):
       
   127         # same
       
   128         hin  = [('if-modified-since', 'Sat, 14 Apr 2012 14:39:32 GM'),
       
   129                ]
       
   130         hout = [('last-modified', 'Sat, 14 Apr 2012 14:39:32 GM'),
       
   131                ]
       
   132         req = _test_cache(hin, hout)
       
   133         self.assertCache(304, req.status_out, 'origin is equal to client')
       
   134 
       
   135     @tag('etag')
       
   136     def test_etag_mismatch(self):
       
   137         #: test the proper behavior of etag only
       
   138         # etag mismatch
       
   139         hin  = [('if-none-match', 'babar'),
       
   140                ]
       
   141         hout = [('etag', 'celestine'),
       
   142                ]
       
   143         req = _test_cache(hin, hout)
       
   144         self.assertCache(None, req.status_out, 'etag mismatch')
       
   145 
       
   146     @tag('etag')
       
   147     def test_etag_match(self):
       
   148         # etag match
       
   149         hin  = [('if-none-match', 'babar'),
       
   150                ]
       
   151         hout = [('etag', 'babar'),
       
   152                ]
       
   153         req = _test_cache(hin, hout)
       
   154         self.assertCache(304, req.status_out, 'etag match')
       
   155         # etag match in multiple
       
   156         hin  = [('if-none-match', 'loutre'),
       
   157                 ('if-none-match', 'babar'),
       
   158                ]
       
   159         hout = [('etag', 'babar'),
       
   160                ]
       
   161         req = _test_cache(hin, hout)
       
   162         self.assertCache(304, req.status_out, 'etag match in multiple')
       
   163         # client use "*" as etag
       
   164         hin  = [('if-none-match', '*'),
       
   165                ]
       
   166         hout = [('etag', 'babar'),
       
   167                ]
       
   168         req = _test_cache(hin, hout)
       
   169         self.assertCache(304, req.status_out, 'client use "*" as etag')
       
   170 
       
   171     @tag('etag', 'last_modified')
       
   172     def test_both(self):
       
   173         #: test the proper behavior of etag only
       
   174         # both wrong
       
   175         hin  = [('if-none-match', 'babar'),
       
   176                 ('if-modified-since', 'Sat, 14 Apr 2012 14:39:32 GM'),
       
   177                ]
       
   178         hout = [('etag', 'loutre'),
       
   179                 ('last-modified', 'Sat, 15 Apr 2012 14:39:32 GM'),
       
   180                ]
       
   181         req = _test_cache(hin, hout)
       
   182         self.assertCache(None, req.status_out, 'both wrong')
       
   183 
       
   184     @tag('etag', 'last_modified')
       
   185     def test_both_etag_mismatch(self):
       
   186         # both etag mismatch
       
   187         hin  = [('if-none-match', 'babar'),
       
   188                 ('if-modified-since', 'Sat, 14 Apr 2012 14:39:32 GM'),
       
   189                ]
       
   190         hout = [('etag', 'loutre'),
       
   191                 ('last-modified', 'Sat, 13 Apr 2012 14:39:32 GM'),
       
   192                ]
       
   193         req = _test_cache(hin, hout)
       
   194         self.assertCache(None, req.status_out, 'both  but etag mismatch')
       
   195 
       
   196     @tag('etag', 'last_modified')
       
   197     def test_both_but_modified(self):
       
   198         # both but modified
       
   199         hin  = [('if-none-match', 'babar'),
       
   200                 ('if-modified-since', 'Sat, 14 Apr 2012 14:39:32 GM'),
       
   201                ]
       
   202         hout = [('etag', 'babar'),
       
   203                 ('last-modified', 'Sat, 15 Apr 2012 14:39:32 GM'),
       
   204                ]
       
   205         req = _test_cache(hin, hout)
       
   206         self.assertCache(None, req.status_out, 'both  but modified')
       
   207 
       
   208     @tag('etag', 'last_modified')
       
   209     def test_both_ok(self):
       
   210         # both ok
       
   211         hin  = [('if-none-match', 'babar'),
       
   212                 ('if-modified-since', 'Sat, 14 Apr 2012 14:39:32 GM'),
       
   213                ]
       
   214         hout = [('etag', 'babar'),
       
   215                 ('last-modified', 'Sat, 13 Apr 2012 14:39:32 GM'),
       
   216                ]
       
   217         req = _test_cache(hin, hout)
       
   218         self.assertCache(304, req.status_out, 'both ok')
       
   219 
       
   220     @tag('etag', 'HEAD')
       
   221     def test_head_verb(self):
       
   222         #: check than FOUND 200 is properly raise without content on HEAD request
       
   223         #: This logic does not really belong here :-/
       
   224         # modified
       
   225         hin  = [('if-none-match', 'babar'),
       
   226                ]
       
   227         hout = [('etag', 'rhino/really-not-babar'),
       
   228                ]
       
   229         req = _test_cache(hin, hout, method='HEAD')
       
   230         self.assertCache(None, req.status_out, 'modifier HEAD verb')
       
   231         # not modified
       
   232         hin  = [('if-none-match', 'babar'),
       
   233                ]
       
   234         hout = [('etag', 'babar'),
       
   235                ]
       
   236         req = _test_cache(hin, hout, method='HEAD')
       
   237         self.assertCache(304, req.status_out, 'not modifier HEAD verb')
       
   238 
       
   239     @tag('etag', 'POST')
       
   240     def test_post_verb(self):
       
   241         # modified
       
   242         hin  = [('if-none-match', 'babar'),
       
   243                ]
       
   244         hout = [('etag', 'rhino/really-not-babar'),
       
   245                ]
       
   246         req = _test_cache(hin, hout, method='POST')
       
   247         self.assertCache(None, req.status_out, 'modifier HEAD verb')
       
   248         # not modified
       
   249         hin  = [('if-none-match', 'babar'),
       
   250                ]
       
   251         hout = [('etag', 'babar'),
       
   252                ]
       
   253         req = _test_cache(hin, hout, method='POST')
       
   254         self.assertCache(412, req.status_out, 'not modifier HEAD verb')
       
   255 
       
   256 
       
   257 alloworig = 'access-control-allow-origin'
       
   258 allowmethods = 'access-control-allow-methods'
       
   259 allowheaders = 'access-control-allow-headers'
       
   260 allowcreds = 'access-control-allow-credentials'
       
   261 exposeheaders = 'access-control-expose-headers'
       
   262 maxage = 'access-control-max-age'
       
   263 
       
   264 requestmethod = 'access-control-request-method'
       
   265 requestheaders = 'access-control-request-headers'
       
   266 
       
   267 class _BaseAccessHeadersTC(CubicWebTC):
       
   268 
       
   269     @contextlib.contextmanager
       
   270     def options(self, **options):
       
   271         for k, values in options.items():
       
   272             self.config.set_option(k, values)
       
   273         try:
       
   274             yield
       
   275         finally:
       
   276             for k in options:
       
   277                 self.config.set_option(k, '')
       
   278     def check_no_cors(self, req):
       
   279         self.assertEqual(None, req.get_response_header(alloworig))
       
   280         self.assertEqual(None, req.get_response_header(allowmethods))
       
   281         self.assertEqual(None, req.get_response_header(allowheaders))
       
   282         self.assertEqual(None, req.get_response_header(allowcreds))
       
   283         self.assertEqual(None, req.get_response_header(exposeheaders))
       
   284         self.assertEqual(None, req.get_response_header(maxage))
       
   285 
       
   286 
       
   287 class SimpleAccessHeadersTC(_BaseAccessHeadersTC):
       
   288 
       
   289     def test_noaccess(self):
       
   290         with self.admin_access.web_request() as req:
       
   291             data = self.app_handle_request(req)
       
   292             self.check_no_cors(req)
       
   293 
       
   294     def test_noorigin(self):
       
   295         with self.options(**{alloworig: '*'}):
       
   296             with self.admin_access.web_request() as req:
       
   297                 data = self.app_handle_request(req)
       
   298                 self.check_no_cors(req)
       
   299 
       
   300     def test_origin_noaccess(self):
       
   301         with self.admin_access.web_request() as req:
       
   302             req.set_request_header('Origin', 'http://www.cubicweb.org')
       
   303             data = self.app_handle_request(req)
       
   304             self.check_no_cors(req)
       
   305 
       
   306     def test_origin_noaccess_bad_host(self):
       
   307         with self.options(**{alloworig: '*'}):
       
   308             with self.admin_access.web_request() as req:
       
   309                 req.set_request_header('Origin', 'http://www.cubicweb.org')
       
   310                 # in these tests, base_url is http://testing.fr/cubicweb/
       
   311                 req.set_request_header('Host', 'badhost.net')
       
   312                 data = self.app_handle_request(req)
       
   313                 self.check_no_cors(req)
       
   314 
       
   315     def test_explicit_origin_noaccess(self):
       
   316         with self.options(**{alloworig: ['http://www.toto.org', 'http://othersite.fr']}):
       
   317             with self.admin_access.web_request() as req:
       
   318                 req.set_request_header('Origin', 'http://www.cubicweb.org')
       
   319                 # in these tests, base_url is http://testing.fr/cubicweb/
       
   320                 req.set_request_header('Host', 'testing.fr')
       
   321                 data = self.app_handle_request(req)
       
   322                 self.check_no_cors(req)
       
   323 
       
   324     def test_origin_access(self):
       
   325         with self.options(**{alloworig: '*'}):
       
   326             with self.admin_access.web_request() as req:
       
   327                 req.set_request_header('Origin', 'http://www.cubicweb.org')
       
   328                 # in these tests, base_url is http://testing.fr/cubicweb/
       
   329                 req.set_request_header('Host', 'testing.fr')
       
   330                 data = self.app_handle_request(req)
       
   331                 self.assertEqual('http://www.cubicweb.org',
       
   332                                  req.get_response_header(alloworig))
       
   333 
       
   334     def test_explicit_origin_access(self):
       
   335         with self.options(**{alloworig: ['http://www.cubicweb.org', 'http://othersite.fr']}):
       
   336             with self.admin_access.web_request() as req:
       
   337                 req.set_request_header('Origin', 'http://www.cubicweb.org')
       
   338                 # in these tests, base_url is http://testing.fr/cubicweb/
       
   339                 req.set_request_header('Host', 'testing.fr')
       
   340                 data = self.app_handle_request(req)
       
   341                 self.assertEqual('http://www.cubicweb.org',
       
   342                                  req.get_response_header(alloworig))
       
   343 
       
   344     def test_origin_access_headers(self):
       
   345         with self.options(**{alloworig: '*',
       
   346                              exposeheaders: ['ExposeHead1', 'ExposeHead2'],
       
   347                              allowheaders: ['AllowHead1', 'AllowHead2'],
       
   348                              allowmethods: ['GET', 'POST', 'OPTIONS']}):
       
   349             with self.admin_access.web_request() as req:
       
   350                 req.set_request_header('Origin', 'http://www.cubicweb.org')
       
   351                 # in these tests, base_url is http://testing.fr/cubicweb/
       
   352                 req.set_request_header('Host', 'testing.fr')
       
   353                 data = self.app_handle_request(req)
       
   354                 self.assertEqual('http://www.cubicweb.org',
       
   355                                  req.get_response_header(alloworig))
       
   356                 self.assertEqual("true",
       
   357                                  req.get_response_header(allowcreds))
       
   358                 self.assertEqual(['ExposeHead1', 'ExposeHead2'],
       
   359                                  req.get_response_header(exposeheaders))
       
   360                 self.assertEqual(None, req.get_response_header(allowmethods))
       
   361                 self.assertEqual(None, req.get_response_header(allowheaders))
       
   362 
       
   363 
       
   364 class PreflightAccessHeadersTC(_BaseAccessHeadersTC):
       
   365 
       
   366     def test_noaccess(self):
       
   367         with self.admin_access.web_request(method='OPTIONS') as req:
       
   368             data = self.app_handle_request(req)
       
   369             self.check_no_cors(req)
       
   370 
       
   371     def test_noorigin(self):
       
   372         with self.options(**{alloworig: '*'}):
       
   373             with self.admin_access.web_request(method='OPTIONS') as req:
       
   374                 data = self.app_handle_request(req)
       
   375                 self.check_no_cors(req)
       
   376 
       
   377     def test_origin_noaccess(self):
       
   378         with self.admin_access.web_request(method='OPTIONS') as req:
       
   379             req.set_request_header('Origin', 'http://www.cubicweb.org')
       
   380             data = self.app_handle_request(req)
       
   381             self.check_no_cors(req)
       
   382 
       
   383     def test_origin_noaccess_bad_host(self):
       
   384         with self.options(**{alloworig: '*'}):
       
   385             with self.admin_access.web_request(method='OPTIONS') as req:
       
   386                 req.set_request_header('Origin', 'http://www.cubicweb.org')
       
   387                 # in these tests, base_url is http://testing.fr/cubicweb/
       
   388                 req.set_request_header('Host', 'badhost.net')
       
   389                 data = self.app_handle_request(req)
       
   390                 self.check_no_cors(req)
       
   391 
       
   392     def test_origin_access(self):
       
   393         with self.options(**{alloworig: '*',
       
   394                              exposeheaders: ['ExposeHead1', 'ExposeHead2'],
       
   395                              allowheaders: ['AllowHead1', 'AllowHead2'],
       
   396                              allowmethods: ['GET', 'POST', 'OPTIONS']}):
       
   397             with self.admin_access.web_request(method='OPTIONS') as req:
       
   398                 req.set_request_header('Origin', 'http://www.cubicweb.org')
       
   399                 # in these tests, base_url is http://testing.fr/cubicweb/
       
   400                 req.set_request_header('Host', 'testing.fr')
       
   401                 req.set_request_header(requestmethod, 'GET')
       
   402 
       
   403                 data = self.app_handle_request(req)
       
   404                 self.assertEqual(200, req.status_out)
       
   405                 self.assertEqual('http://www.cubicweb.org',
       
   406                                  req.get_response_header(alloworig))
       
   407                 self.assertEqual("true",
       
   408                                  req.get_response_header(allowcreds))
       
   409                 self.assertEqual(set(['GET', 'POST', 'OPTIONS']),
       
   410                                  req.get_response_header(allowmethods))
       
   411                 self.assertEqual(set(['AllowHead1', 'AllowHead2']),
       
   412                                  req.get_response_header(allowheaders))
       
   413                 self.assertEqual(None,
       
   414                                  req.get_response_header(exposeheaders))
       
   415 
       
   416 
       
   417 if __name__ == '__main__':
       
   418     unittest_main()