"""
Inheritance Diagram
-------------------
.. inheritance-diagram:: kotti.testing
"""
import os
from os.path import dirname
from os.path import join
from unittest import TestCase
from warnings import catch_warnings
import transaction
from pyramid import testing
from pyramid.events import NewResponse
from pyramid.interfaces import ILocation
from pyramid.security import ALL_PERMISSIONS
from pytest import mark
from zope.interface import implementer
# re-enable deprecation warnings during test runs
# however, let the `ImportWarning` produced by Babel's
# `localedata.py` vs `localedata/` show up once...
with catch_warnings():
pass
# import compiler
# localedata, compiler # make pyflakes happy... :p
# py.test markers (see http://pytest.org/latest/example/markers.html)
user = mark.user
BASE_URL = "http://localhost:6543"
[docs]class Dummy(dict):
# noinspection PyMissingConstructor
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
[docs]class DummyRequest(testing.DummyRequest):
is_xhr = False
POST = dict()
user = None
referrer = None
@staticmethod
def is_response(ob):
return (
hasattr(ob, "app_iter")
and hasattr(ob, "headerlist")
and hasattr(ob, "status")
)
# noinspection PyPep8Naming
[docs] @classmethod
def blank(cls, path, environ=None, base_url=None, headers=None, POST=None, **kw):
"""
``request.blank`` is used in Kotti only when assigning slots, where
the POST parameters are faked as a querystring.
"""
from urllib.parse import parse_qsl
def _decode(body):
if not body:
return {}
return {x: y for x, y in parse_qsl(body)}
if POST and isinstance(POST, bytes):
POST = POST.decode()
if POST and isinstance(POST, str):
POST = _decode(POST)
req = testing.DummyRequest(
path=path, environ=environ, headers=headers, cookies=None, post=POST, **kw
)
return req
def asset(name):
import kotti
return open(join(dirname(kotti.__file__), "tests", name), "rb")
[docs]def includeme_login(config):
""" Pyramid includeme hook.
:param config: app config
:type config: :class:`pyramid.config.Configurator`
"""
config.add_view(login_view, name="login", renderer="kotti:templates/login.pt")
[docs]def includeme_layout(config):
""" Pyramid includeme hook.
:param config: app config
:type config: :class:`pyramid.config.Configurator`
"""
# override edit master layout with view master layout
config.override_asset(
to_override="kotti:templates/edit/master.pt",
override_with="kotti:templates/view/master.pt",
)
def login_view(request):
return {}
def dummy_search(search_term, request):
return "Not found. Sorry!"
def testing_db_url():
return os.environ.get("KOTTI_TEST_DB_STRING", "sqlite://")
def _init_testing_db():
from sqlalchemy import create_engine
from kotti import get_settings
from kotti.resources import initialize_sql
database_url = testing_db_url()
get_settings()["sqlalchemy.url"] = database_url
session = initialize_sql(create_engine(database_url), drop_all=True)
return session
def _populator():
from kotti import DBSession
from kotti.resources import Document
from kotti.populate import populate
populate()
for doc in DBSession.query(Document)[1:]:
DBSession.delete(doc)
transaction.commit()
def _turn_warnings_into_errors(): # pragma: no cover
# turn all warnings into errors, but let the `ImportWarning`
# produced by Babel's `localedata.py` vs `localedata/` show up once...
from babel import localedata
localedata # make pyflakes happy... :p
from warnings import filterwarnings
filterwarnings("error")
# noinspection PyPep8Naming
def setUp(init_db=True, **kwargs):
# _turn_warnings_into_errors()
from kotti import _resolve_dotted
from kotti import conf_defaults
tearDown()
settings = conf_defaults.copy()
settings["kotti.secret"] = "secret"
settings["kotti.secret2"] = "secret2"
settings["kotti.populators"] = "kotti.testing._populator"
settings["pyramid_deform.tempdir"] = "/tmp"
settings.update(kwargs.get("settings", {}))
settings = _resolve_dotted(settings)
kwargs["settings"] = settings
config = testing.setUp(**kwargs)
config.add_default_renderers()
if init_db:
_init_testing_db()
transaction.begin()
return config
# noinspection PyPep8Naming
def tearDown():
from depot.manager import DepotManager
from kotti import events
from kotti import security
from kotti.message import _inject_mailer
# These should arguable use the configurator, so they don't need
# to be torn down separately:
events.clear()
security.reset()
_inject_mailer[:] = []
transaction.abort()
DepotManager._clear()
testing.tearDown()
[docs]class UnitTestBase(TestCase):
[docs] def setUp(self, **kwargs):
self.config = setUp(**kwargs)
[docs] def tearDown(self):
tearDown()
[docs]class EventTestBase(TestCase):
[docs] def setUp(self, **kwargs):
super().setUp(**kwargs)
self.config.include("kotti.events")
# Functional ----
def _functional_includeme(config):
from kotti import DBSession
def expire(event):
DBSession.flush()
DBSession.expire_all()
config.add_subscriber(expire, NewResponse)
def _zope_testbrowser_pyquery(self):
from pyquery import PyQuery
return PyQuery(self.contents.replace('xmlns="http://www.w3.org/1999/xhtml', ""))
# noinspection PyPep8Naming
def setUpFunctional(global_config=None, **settings):
from kotti import main
from zope.testbrowser.wsgi import Browser
from webtest import TestApp
tearDown()
_settings = {
"sqlalchemy.url": testing_db_url(),
"kotti.secret": "secret",
"kotti.site_title": "Website des Kottbusser Tors", # for mailing
"kotti.populators": "kotti.testing._populator",
"mail.default_sender": "kotti@localhost",
"pyramid.includes": "kotti.testing._functional_includeme",
}
_settings.update(settings)
host, port = BASE_URL.split(":")[-2:]
app = main({}, **_settings)
Browser.pyquery = property(_zope_testbrowser_pyquery)
return dict(
Browser=lambda: Browser(
"http://{}:{}/".format(host[2:], int(port)), wsgi_app=app
),
browser=Browser("http://{}:{}/".format(host[2:], int(port)), wsgi_app=app),
test_app=TestApp(app),
)
[docs]class FunctionalTestBase(TestCase):
BASE_URL = BASE_URL
[docs] def setUp(self, **kwargs):
self.__dict__.update(setUpFunctional(**kwargs))
[docs] def tearDown(self):
tearDown()
def login(self, login="admin", password="secret"):
return self.test_app.post(
"/@@login",
{"login": login, "password": password, "submit": "submit"},
status=302,
)
[docs]@implementer(ILocation)
class RootFactory(dict):
__name__ = "" # root is required to have an empty name!
__parent__ = None
__acl__ = [("Allow", "role:admin", ALL_PERMISSIONS)]
def __init__(self, request):
super().__init__()
def dummy_view(context, request):
return {}
[docs]def include_testing_view(config):
""" Pyramid includeme hook.
:param config: app config
:type config: :class:`pyramid.config.Configurator`
"""
config.add_view(
dummy_view, context=RootFactory, renderer="kotti:tests/testing_view.pt"
)
config.add_view(
dummy_view,
name="secured",
permission="view",
context=RootFactory,
renderer="kotti:tests/testing_view.pt",
)
# noinspection PyPep8Naming
def setUpFunctionalStrippedDownApp(global_config=None, **settings):
# An app that doesn't use Nodes at all
_settings = {
"kotti.base_includes": (
"kotti kotti.views kotti.views.login kotti.views.users " "kotti.views.view"
),
"kotti.use_tables": "principals",
"kotti.populators": "kotti.populate.populate_users",
"pyramid.includes": "kotti.testing.include_testing_view",
"kotti.root_factory": "kotti.testing.RootFactory",
"kotti.site_title": "My Stripped Down Kotti",
}
_settings.update(settings)
return setUpFunctional(global_config, **_settings)
# noinspection PyPep8Naming
def registerDummyMailer():
from pyramid_mailer.mailer import DummyMailer
from kotti.message import _inject_mailer
mailer = DummyMailer()
_inject_mailer.append(mailer)
return mailer
# set up deprecation warnings
from zope.deprecation.deprecation import deprecated # noqa
for item in UnitTestBase, EventTestBase, FunctionalTestBase, _init_testing_db:
name = getattr(item, "__name__", item)
deprecated(
name,
"Unittest-style tests are deprecated as of Kotti 0.7. "
"Please use pytest function arguments instead.",
)