Commit a267e72d authored by Simon Chabot's avatar Simon Chabot
Browse files

merge 3.32 into default

......@@ -698,4 +698,5 @@ b43a0459d50017c6a85a7def26fdd551c2308240 3.32.0
9965ff53bda2cbe0378a32d4d34feb94641116b2 3.32.2
e106c287f71a1f49e681214a20ee26e81f695043 3.31.2
ef46f909341c31472545c693e63d4ad79a3087da 3.31.3
9c540973cf4b947c26c7b474fb9d9659444396a3 3.32.3
8c6c6e724978d7eafaf549f676446c9e232b7047 3.33.0
......@@ -19,8 +19,13 @@
http server
"""
import http.client
import random
import socket
from urllib.parse import urlparse
from webtest.http import StopableWSGIServer
from cubicweb.pyramid.test import _BasePyramidCWTest
def get_available_port(ports_scan):
......@@ -43,7 +48,7 @@ def get_available_port(ports_scan):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("localhost", port))
except OSError as err:
except socket.error as err:
if err.args[0] in (111, 106):
return port
finally:
......@@ -51,3 +56,87 @@ def get_available_port(ports_scan):
raise RuntimeError(
"get_available_port([ports_range]) cannot find an available port"
)
class CubicWebServerTC(_BasePyramidCWTest):
"""Base class for running a test web server."""
ports_range = range(7000, 8000)
def start_server(self):
app = self._generate_pyramid_config().make_wsgi_app()
self.httpd = StopableWSGIServer.create(app, port=self.config["port"])
self.httpd.wait()
parseurl = urlparse(self.config["base-url"])
assert parseurl.port == self.config["port"], (
self.config["base-url"],
self.config["port"],
)
self._web_test_cnx = http.client.HTTPConnection(
parseurl.hostname, parseurl.port
)
self._ident_cookie = None
def stop_server(self, timeout=15):
if self._web_test_cnx is None:
self.web_logout()
self._web_test_cnx.close()
self.httpd.shutdown()
def web_login(self, user=None, passwd=None):
"""Log the current http session for the provided credential
If no user is provided, admin connection are used.
"""
if user is None:
user = self.admlogin
passwd = self.admpassword
if passwd is None:
passwd = user
response = self.web_get("login?__login=%s&__password=%s" % (user, passwd))
assert response.status == http.client.SEE_OTHER, response.status
self._ident_cookie = response.getheader("Set-Cookie")
assert self._ident_cookie
return True
def web_logout(self, user="admin", pwd=None):
"""Log out current http user"""
if self._ident_cookie is not None:
self.web_get("logout")
self._ident_cookie = None
def web_request(self, path="", method="GET", body=None, headers=None):
"""Return an http.client.HTTPResponse object for the specified path
Use available credential if available.
"""
if headers is None:
headers = {}
if self._ident_cookie is not None:
assert "Cookie" not in headers
headers["Cookie"] = self._ident_cookie
self._web_test_cnx.request(method, "/" + path, headers=headers, body=body)
response = self._web_test_cnx.getresponse()
response.body = response.read() # to chain request
response.read = lambda: response.body
return response
def web_get(self, path="", body=None, headers=None):
return self.web_request(path=path, body=body, headers=headers)
def setUp(self):
super().setUp()
port = get_available_port(self.ports_range)
self.config.global_set_option("port", port) # force rewrite here
self.config.global_set_option("base-url", "http://127.0.0.1:%d/" % port)
# call load_configuration again to let the config reset its datadir_url
self.config.load_configuration()
self.start_server()
def tearDown(self):
self.stop_server()
super().tearDown()
# copyright 2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact https://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 <https://www.gnu.org/licenses/>.
"""unittest for cubicweb.devtools.httptest module"""
import http.client
from logilab.common.testlib import Tags
from cubicweb.devtools.httptest import CubicWebServerTC
class WsgiCWAnonTC(CubicWebServerTC):
settings = {"cubicweb.bwcompat": True}
def test_response(self):
try:
response = self.web_get()
except http.client.NotConnected as ex:
self.fail("Can't connection to test server: %s" % ex)
def test_response_anon(self):
response = self.web_get()
self.assertEqual(response.status, http.client.OK)
def test_base_url(self):
if self.config["base-url"] not in self.web_get().read().decode("ascii"):
self.fail("no mention of base url in retrieved page")
class WsgiCWIdentTC(CubicWebServerTC):
settings = {"cubicweb.bwcompat": True}
test_db_id = "httptest-cwident"
anonymous_allowed = False
tags = CubicWebServerTC.tags | Tags(("auth",))
def test_response_denied(self):
response = self.web_get()
self.assertEqual(response.status, http.client.FORBIDDEN)
def test_login(self):
response = self.web_get()
if response.status != http.client.FORBIDDEN:
raise Exception(
'Already authenticated, "test_response_denied" must have failed'
)
# login
self.web_login(self.admlogin, self.admpassword)
response = self.web_get()
self.assertEqual(response.status, http.client.OK, response.body)
# logout
self.web_logout()
response = self.web_get()
self.assertEqual(response.status, http.client.FORBIDDEN, response.body)
if __name__ == "__main__":
from logilab.common.testlib import unittest_main
unittest_main()
from pkg_resources import VersionConflict
try:
from cwtags import __pkginfo__
except ImportError:
pass
else:
if __pkginfo__.numversion < (1, 2, 3):
raise VersionConflict(
"This version ({}) of cwtags is incompatible with CubicWeb >= 3.32. "
"Please, upgrade your version of cwtags to >= 1.2.3 before running "
"this migration.".format(__pkginfo__.version)
)
......@@ -112,18 +112,39 @@ class TestApp(webtest.TestApp):
return response
class PyramidCWTest(CubicWebTestTC):
class _BasePyramidCWTest(CubicWebTestTC):
settings = {}
@classmethod
def init_config(cls, config):
super(PyramidCWTest, cls).init_config(config)
super().init_config(config)
config.global_set_option("anonymous-user", "anon")
config.in_debug_mode = True
def _generate_pyramid_config(self):
settings = {
"cubicweb.bwcompat": False,
"cubicweb.session.secret": "test",
}
settings.update(self.settings)
pyramid_config = Configurator(settings=settings)
pyramid_config.registry["cubicweb.repository"] = self.repo
pyramid_config.include("cubicweb.pyramid")
self.includeme(pyramid_config)
self.pyr_registry = pyramid_config.registry
return pyramid_config
def includeme(self, config):
config.registry.settings["pyramid.csrf_trusted_origins"] = ACCEPTED_ORIGINS
class PyramidCWTest(_BasePyramidCWTest):
def setUp(self):
# Skip CubicWebTestTC setUp
super(CubicWebTestTC, self).setUp()
super().setUp()
settings = {
"cubicweb.bwcompat": False,
"cubicweb.session.secret": "test",
......@@ -142,6 +163,3 @@ class PyramidCWTest(CubicWebTestTC):
admin_login=self.admlogin,
admin_password=self.admpassword,
)
def includeme(self, config):
config.registry.settings["pyramid.csrf_trusted_origins"] = ACCEPTED_ORIGINS
......@@ -363,16 +363,16 @@ class View(AppObject):
if tr:
label = display_name(self._cw, label)
if table:
w("<th>%s</th>", label)
w("<th>%s</th>", label, escape=False)
else:
w('<span class="label">%s</span> ', label)
w('<span class="label">%s</span> ', label, escape=False)
if table:
if not (show_label and label):
w('<td colspan="2">%s</td></tr>', value)
w('<td colspan="2">%s</td></tr>', value, escape=False)
else:
w("<td>%s</td></tr>", value)
w("<td>%s</td></tr>", value, escape=False)
else:
w("<span>%s</span></div>", value)
w("<span>%s</span></div>", value, escape=False)
# concrete views base classes #################################################
......
......@@ -1676,7 +1676,7 @@ class FacetVocabularyWidget(htmlwidgets.HTMLWidget):
# If it is overflowed one must add padding to compensate for the vertical
# scrollbar; given current css values, 4 blanks work perfectly ...
padding = "&#160;" * self.scrollbar_padding_factor if overflow else ""
w("<span>%s</span>", xml_escape(label))
w("<span>%s</span>", label)
w(padding)
w("</div>")
......
......@@ -227,7 +227,11 @@ class CreationFormView(EditionFormView):
% (entity.e_schema, linkto_type, rtype, entity.e_schema)
)
msg = title % {"linkto": self._cw.view("incontext", linkto_rset)}
self.w('<div class="formTitle notransform"><span>%s</span></div>', msg)
self.w(
'<div class="formTitle notransform"><span>%s</span></div>',
msg,
escape=False,
)
else:
super().form_title(entity)
......
3.32.3 (2021-08-31)
===================
🎉 New features
--------------
- migration: add a migration script to warn about incompatibility of cwtags. (https://forge.extranet.logilab.fr/cubicweb/cubicweb/-/issues/367)
👷 Bug fixes
-----------
- bringing back CubicWebServerTC and porting it to pyramid
- fix bad escaped values in web views
- pkg: since we added csrf mecanism, we need pyramid >= 1.9
- test_newcube were broken because we removed cubicweb-*.spec file but didn't updated the tests
3.32.2 (2021-07-30)
===================
🎉 New features
......
psycopg2-binary
ldap3<3,>2
webtest
......@@ -75,7 +75,7 @@ setup(
"filelock",
"rdflib >= 5.0.0, < 6.0.0",
"rdflib-jsonld",
"pyramid >= 1.5.0, < 2.0.0",
"pyramid >= 1.9.0, < 2.0.0",
"waitress >= 1.4.0, < 2.0.0",
"wsgicors >= 0.3",
"pyramid_multiauth",
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment