[sqlite] Fix integrity error parsing
Up to sqlite 3.7, integrity error message may be in the singular form
if only one column is in the unique index.
Closes #5224632
# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr## This file is part of CubicWeb.## CubicWeb is free software: you can redistribute it and/or modify it under the# terms of the GNU Lesser General Public License as published by the Free# Software Foundation, either version 2.1 of the License, or (at your option)# any later version.## CubicWeb is distributed in the hope that it will be useful, but WITHOUT# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more# details.## You should have received a copy of the GNU Lesser General Public License along# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.importcontextlibfromlogilab.common.testlibimportTestCase,unittest_main,tag,Tagsfromcubicweb.devtools.fakeimportFakeRequestfromcubicweb.devtools.testlibimportCubicWebTCdef_test_cache(hin,hout,method='GET'):"""forge and process an HTTP request using given headers in/out and method, then return it once its .is_client_cache_valid() method has been called. req.status_out is None if the page should have been calculated. """# forge requestreq=FakeRequest(method=method)forkey,valueinhin:req._headers_in.addRawHeader(key,str(value))forkey,valueinhout:req.headers_out.addRawHeader(key,str(value))# processreq.status_out=Nonereq.is_client_cache_valid()returnreqclassHTTPCache(TestCase):"""Check that the http cache logiac work as expected (as far as we understood the RFC) """tags=TestCase.tags|Tags('http','cache')defassertCache(self,expected,status,situation=''):"""simple assert for nicer message"""ifexpected!=status:ifexpectedisNone:expected="MODIFIED"ifstatusisNone:status="MODIFIED"msg='expected %r got %r'%(expected,status)ifsituation:msg="%s - when: %s"%(msg,situation)self.fail(msg)deftest_IN_none_OUT_none(self):#: test that no caching is requested when not data is available#: on any sidereq=_test_cache((),())self.assertIsNone(req.status_out)deftest_IN_Some_OUT_none(self):#: test that no caching is requested when no data is available#: server (origin) sidehin=[('if-modified-since','Sat, 14 Apr 2012 14:39:32 GM'),]req=_test_cache(hin,())self.assertIsNone(req.status_out)hin=[('if-none-match','babar/huitre'),]req=_test_cache(hin,())self.assertIsNone(req.status_out)hin=[('if-modified-since','Sat, 14 Apr 2012 14:39:32 GM'),('if-none-match','babar/huitre'),]req=_test_cache(hin,())self.assertIsNone(req.status_out)deftest_IN_none_OUT_Some(self):#: test that no caching is requested when no data is provided#: by the clienthout=[('last-modified','Sat, 14 Apr 2012 14:39:32 GM'),]req=_test_cache((),hout)self.assertIsNone(req.status_out)hout=[('etag','babar/huitre'),]req=_test_cache((),hout)self.assertIsNone(req.status_out)hout=[('last-modified','Sat, 14 Apr 2012 14:39:32 GM'),('etag','babar/huitre'),]req=_test_cache((),hout)self.assertIsNone(req.status_out)@tag('last_modified')deftest_last_modified_newer(self):#: test the proper behavior of modification date only# newerhin=[('if-modified-since','Sat, 13 Apr 2012 14:39:32 GM'),]hout=[('last-modified','Sat, 14 Apr 2012 14:39:32 GM'),]req=_test_cache(hin,hout)self.assertCache(None,req.status_out,'origin is newer than client')@tag('last_modified')deftest_last_modified_older(self):# olderhin=[('if-modified-since','Sat, 15 Apr 2012 14:39:32 GM'),]hout=[('last-modified','Sat, 14 Apr 2012 14:39:32 GM'),]req=_test_cache(hin,hout)self.assertCache(304,req.status_out,'origin is older than client')@tag('last_modified')deftest_last_modified_same(self):# samehin=[('if-modified-since','Sat, 14 Apr 2012 14:39:32 GM'),]hout=[('last-modified','Sat, 14 Apr 2012 14:39:32 GM'),]req=_test_cache(hin,hout)self.assertCache(304,req.status_out,'origin is equal to client')@tag('etag')deftest_etag_mismatch(self):#: test the proper behavior of etag only# etag mismatchhin=[('if-none-match','babar'),]hout=[('etag','celestine'),]req=_test_cache(hin,hout)self.assertCache(None,req.status_out,'etag mismatch')@tag('etag')deftest_etag_match(self):# etag matchhin=[('if-none-match','babar'),]hout=[('etag','babar'),]req=_test_cache(hin,hout)self.assertCache(304,req.status_out,'etag match')# etag match in multiplehin=[('if-none-match','loutre'),('if-none-match','babar'),]hout=[('etag','babar'),]req=_test_cache(hin,hout)self.assertCache(304,req.status_out,'etag match in multiple')# client use "*" as etaghin=[('if-none-match','*'),]hout=[('etag','babar'),]req=_test_cache(hin,hout)self.assertCache(304,req.status_out,'client use "*" as etag')@tag('etag','last_modified')deftest_both(self):#: test the proper behavior of etag only# both wronghin=[('if-none-match','babar'),('if-modified-since','Sat, 14 Apr 2012 14:39:32 GM'),]hout=[('etag','loutre'),('last-modified','Sat, 15 Apr 2012 14:39:32 GM'),]req=_test_cache(hin,hout)self.assertCache(None,req.status_out,'both wrong')@tag('etag','last_modified')deftest_both_etag_mismatch(self):# both etag mismatchhin=[('if-none-match','babar'),('if-modified-since','Sat, 14 Apr 2012 14:39:32 GM'),]hout=[('etag','loutre'),('last-modified','Sat, 13 Apr 2012 14:39:32 GM'),]req=_test_cache(hin,hout)self.assertCache(None,req.status_out,'both but etag mismatch')@tag('etag','last_modified')deftest_both_but_modified(self):# both but modifiedhin=[('if-none-match','babar'),('if-modified-since','Sat, 14 Apr 2012 14:39:32 GM'),]hout=[('etag','babar'),('last-modified','Sat, 15 Apr 2012 14:39:32 GM'),]req=_test_cache(hin,hout)self.assertCache(None,req.status_out,'both but modified')@tag('etag','last_modified')deftest_both_ok(self):# both okhin=[('if-none-match','babar'),('if-modified-since','Sat, 14 Apr 2012 14:39:32 GM'),]hout=[('etag','babar'),('last-modified','Sat, 13 Apr 2012 14:39:32 GM'),]req=_test_cache(hin,hout)self.assertCache(304,req.status_out,'both ok')@tag('etag','HEAD')deftest_head_verb(self):#: check than FOUND 200 is properly raise without content on HEAD request#: This logic does not really belong here :-/# modifiedhin=[('if-none-match','babar'),]hout=[('etag','rhino/really-not-babar'),]req=_test_cache(hin,hout,method='HEAD')self.assertCache(None,req.status_out,'modifier HEAD verb')# not modifiedhin=[('if-none-match','babar'),]hout=[('etag','babar'),]req=_test_cache(hin,hout,method='HEAD')self.assertCache(304,req.status_out,'not modifier HEAD verb')@tag('etag','POST')deftest_post_verb(self):# modifiedhin=[('if-none-match','babar'),]hout=[('etag','rhino/really-not-babar'),]req=_test_cache(hin,hout,method='POST')self.assertCache(None,req.status_out,'modifier HEAD verb')# not modifiedhin=[('if-none-match','babar'),]hout=[('etag','babar'),]req=_test_cache(hin,hout,method='POST')self.assertCache(412,req.status_out,'not modifier HEAD verb')@tag('expires')deftest_expires_added(self):#: Check that Expires header is added:#: - when the page is modified#: - when none was already presenthin=[('if-none-match','babar'),]hout=[('etag','rhino/really-not-babar'),]req=_test_cache(hin,hout)self.assertCache(None,req.status_out,'modifier HEAD verb')value=req.headers_out.getHeader('expires')self.assertIsNotNone(value)@tag('expires')deftest_expires_not_added(self):#: Check that Expires header is not added if NOT-MODIFIEDhin=[('if-none-match','babar'),]hout=[('etag','babar'),]req=_test_cache(hin,hout)self.assertCache(304,req.status_out,'not modifier HEAD verb')value=req.headers_out.getHeader('expires')self.assertIsNone(value)@tag('expires')deftest_expires_no_overwrite(self):#: Check that cache does not overwrite existing Expires headerhin=[('if-none-match','babar'),]DATE='Sat, 13 Apr 2012 14:39:32 GM'hout=[('etag','rhino/really-not-babar'),('expires',DATE),]req=_test_cache(hin,hout)self.assertCache(None,req.status_out,'not modifier HEAD verb')value=req.headers_out.getRawHeaders('expires')self.assertEqual(value,[DATE])alloworig='access-control-allow-origin'allowmethods='access-control-allow-methods'allowheaders='access-control-allow-headers'allowcreds='access-control-allow-credentials'exposeheaders='access-control-expose-headers'maxage='access-control-max-age'requestmethod='access-control-request-method'requestheaders='access-control-request-headers'class_BaseAccessHeadersTC(CubicWebTC):@contextlib.contextmanagerdefoptions(self,**options):fork,valuesinoptions.items():self.config.set_option(k,values)try:yieldfinally:forkinoptions:self.config.set_option(k,'')defcheck_no_cors(self,req):self.assertEqual(None,req.get_response_header(alloworig))self.assertEqual(None,req.get_response_header(allowmethods))self.assertEqual(None,req.get_response_header(allowheaders))self.assertEqual(None,req.get_response_header(allowcreds))self.assertEqual(None,req.get_response_header(exposeheaders))self.assertEqual(None,req.get_response_header(maxage))classSimpleAccessHeadersTC(_BaseAccessHeadersTC):deftest_noaccess(self):withself.admin_access.web_request()asreq:data=self.app_handle_request(req)self.check_no_cors(req)deftest_noorigin(self):withself.options(**{alloworig:'*'}):withself.admin_access.web_request()asreq:data=self.app_handle_request(req)self.check_no_cors(req)deftest_origin_noaccess(self):withself.admin_access.web_request()asreq:req.set_request_header('Origin','http://www.cubicweb.org')data=self.app_handle_request(req)self.check_no_cors(req)deftest_origin_noaccess_bad_host(self):withself.options(**{alloworig:'*'}):withself.admin_access.web_request()asreq:req.set_request_header('Origin','http://www.cubicweb.org')# in these tests, base_url is http://testing.fr/cubicweb/req.set_request_header('Host','badhost.net')data=self.app_handle_request(req)self.check_no_cors(req)deftest_explicit_origin_noaccess(self):withself.options(**{alloworig:['http://www.toto.org','http://othersite.fr']}):withself.admin_access.web_request()asreq:req.set_request_header('Origin','http://www.cubicweb.org')# in these tests, base_url is http://testing.fr/cubicweb/req.set_request_header('Host','testing.fr')data=self.app_handle_request(req)self.check_no_cors(req)deftest_origin_access(self):withself.options(**{alloworig:'*'}):withself.admin_access.web_request()asreq:req.set_request_header('Origin','http://www.cubicweb.org')# in these tests, base_url is http://testing.fr/cubicweb/req.set_request_header('Host','testing.fr')data=self.app_handle_request(req)self.assertEqual('http://www.cubicweb.org',req.get_response_header(alloworig))deftest_explicit_origin_access(self):withself.options(**{alloworig:['http://www.cubicweb.org','http://othersite.fr']}):withself.admin_access.web_request()asreq:req.set_request_header('Origin','http://www.cubicweb.org')# in these tests, base_url is http://testing.fr/cubicweb/req.set_request_header('Host','testing.fr')data=self.app_handle_request(req)self.assertEqual('http://www.cubicweb.org',req.get_response_header(alloworig))deftest_origin_access_headers(self):withself.options(**{alloworig:'*',exposeheaders:['ExposeHead1','ExposeHead2'],allowheaders:['AllowHead1','AllowHead2'],allowmethods:['GET','POST','OPTIONS']}):withself.admin_access.web_request()asreq:req.set_request_header('Origin','http://www.cubicweb.org')# in these tests, base_url is http://testing.fr/cubicweb/req.set_request_header('Host','testing.fr')data=self.app_handle_request(req)self.assertEqual('http://www.cubicweb.org',req.get_response_header(alloworig))self.assertEqual("true",req.get_response_header(allowcreds))self.assertEqual(['ExposeHead1','ExposeHead2'],req.get_response_header(exposeheaders))self.assertEqual(None,req.get_response_header(allowmethods))self.assertEqual(None,req.get_response_header(allowheaders))classPreflightAccessHeadersTC(_BaseAccessHeadersTC):deftest_noaccess(self):withself.admin_access.web_request(method='OPTIONS')asreq:data=self.app_handle_request(req)self.check_no_cors(req)deftest_noorigin(self):withself.options(**{alloworig:'*'}):withself.admin_access.web_request(method='OPTIONS')asreq:data=self.app_handle_request(req)self.check_no_cors(req)deftest_origin_noaccess(self):withself.admin_access.web_request(method='OPTIONS')asreq:req.set_request_header('Origin','http://www.cubicweb.org')data=self.app_handle_request(req)self.check_no_cors(req)deftest_origin_noaccess_bad_host(self):withself.options(**{alloworig:'*'}):withself.admin_access.web_request(method='OPTIONS')asreq:req.set_request_header('Origin','http://www.cubicweb.org')# in these tests, base_url is http://testing.fr/cubicweb/req.set_request_header('Host','badhost.net')data=self.app_handle_request(req)self.check_no_cors(req)deftest_origin_access(self):withself.options(**{alloworig:'*',exposeheaders:['ExposeHead1','ExposeHead2'],allowheaders:['AllowHead1','AllowHead2'],allowmethods:['GET','POST','OPTIONS']}):withself.admin_access.web_request(method='OPTIONS')asreq:req.set_request_header('Origin','http://www.cubicweb.org')# in these tests, base_url is http://testing.fr/cubicweb/req.set_request_header('Host','testing.fr')req.set_request_header(requestmethod,'GET')data=self.app_handle_request(req)self.assertEqual(200,req.status_out)self.assertEqual('http://www.cubicweb.org',req.get_response_header(alloworig))self.assertEqual("true",req.get_response_header(allowcreds))self.assertEqual(set(['GET','POST','OPTIONS']),req.get_response_header(allowmethods))self.assertEqual(set(['AllowHead1','AllowHead2']),req.get_response_header(allowheaders))self.assertEqual(None,req.get_response_header(exposeheaders))if__name__=='__main__':unittest_main()