Return error(s) when page routes or assets do not resolve
authorKarl O. Pinc <kop@karlpinc.com>
Tue, 8 Dec 2020 17:58:49 +0000 (11:58 -0600)
committerKarl O. Pinc <kop@karlpinc.com>
Tue, 8 Dec 2020 19:29:45 +0000 (13:29 -0600)
README.rst
src/pgwui_common/assets.py [new file with mode: 0644]
src/pgwui_common/exceptions.py
src/pgwui_common/urls.py
tests/test_assets.py [new file with mode: 0644]
tests/test_urls.py

index cc52ba436c83187bcba1f7b522669ee363562e67..d06e4dc25ed918a272449da47e9abfd5d278e20f 100644 (file)
@@ -49,6 +49,9 @@ PGWUI_Common provides:
   * Code used to establish `routes`_, called by PGWUI_Server
     or whatever else is used to configure `Pyramid`_.
 
+  * Code used to override `assets`_, called by PGWUI_Server
+    or whatever else is used to configure `Pyramid`_.
+
   * Functionality which validates all installed PGWUI component
     settings.  Validation happens when the PGWUI_Common component
     is configured.
@@ -193,3 +196,4 @@ provided by `The Dian Fossey Gorilla Fund
 .. _Pyramid: https://trypyramid.com/
 .. _Pyramid's: `Pyramid`_
 .. _routes: https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/urldispatch.html
+.. _assets: https://docs.pylonsproject.org/projects/pyramid/en/1.10-branch/narr/assets.html
diff --git a/src/pgwui_common/assets.py b/src/pgwui_common/assets.py
new file mode 100644 (file)
index 0000000..248d37f
--- /dev/null
@@ -0,0 +1,38 @@
+# Copyright (C) 2018, 2020 The Meme Factory, Inc.  http://www.karlpinc.com/
+
+# This file is part of PGWUI_Common.
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program 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
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public
+# License along with this program.  If not, see
+# <http://www.gnu.org/licenses/>.
+#
+
+# Karl O. Pinc <kop@karlpinc.com>
+
+'''Override assets based on settings
+'''
+
+import logging
+
+# Logging
+log = logging.getLogger(__name__)
+
+
+def override_assets(config, settings):
+    pgwui = settings['pgwui']
+    if 'override_assets' not in pgwui:
+        return
+
+    for asset, replacement in pgwui['override_assets'].items():
+        config.override_asset(asset, replacement)
+        log.debug(f'Overriding asset ({asset}) with ({replacement})')
index 3ab67841bf91dfe3db872e1da133bad3bf7f29e7..cd69831c1a32d0dbc0c0d39f1c6c5a4eb9dabdaf 100644 (file)
@@ -125,6 +125,26 @@ class NotBooleanSettingError(Error):
             .format(key))
 
 
+class BadPathError(Error):
+    pass
+
+
+class BadRouteError(BadPathError):
+    def __init__(self, page, ex):
+        super().__init__(
+            page, ex,
+            f'The "pgwui:{page}:source" configuration setting refers to '
+            'a route that does not exist')
+
+
+class BadAssetError(BadPathError):
+    def __init__(self, page, ex):
+        super().__init__(
+            page, ex,
+            f'The "pgwui:{page}:source" configuration setting refers to '
+            'an asset that does not exist')
+
+
 class ViewError(Error):
     pass
 
@@ -159,19 +179,3 @@ class BadPageIsADirectoryError(BadPageError):
             page, ex,
             f'The "pgwui:{page}:source" configuration setting refers to '
             f'a directory ({ex.filename}), not a file')
-
-
-class BadRouteError(BadPageError):
-    def __init__(self, page, ex):
-        super().__init__(
-            page, ex,
-            f'The "pgwui:{page}:source" configuration setting refers to '
-            'a route that does not exist')
-
-
-class BadAssetError(BadPageError):
-    def __init__(self, page, ex):
-        super().__init__(
-            page, ex,
-            f'The "pgwui:{page}:source" configuration setting refers to '
-            'an asset that does not exist')
index 63ae9cd5d3164adb70906110fce092e79d52293e..fa5fb98114b1f7a8488e9138097bfc7dc894b64e 100644 (file)
@@ -76,15 +76,18 @@ def set_menu_url(request, urls):
         try:
             menu_url = request.route_path('pgwui_menu')
         except KeyError:
-            return
+            return []    # the pgwui_menu component is optional
+    except ex.BadPathError as exp:
+        return [exp]
     if menu_url != urls['pgwui_home']:
         urls['pgwui_menu'] = menu_url
+    return []
 
 
 def set_component_urls(request, urls):
     '''Add urls for each pgwui component to the 'urls' dict
     '''
-    set_menu_url(request, urls)
+    errors = set_menu_url(request, urls)
     components = find_pgwui_components()
     if 'pgwui_menu' in components:
         components.remove('pgwui_menu')
@@ -97,13 +100,24 @@ def set_component_urls(request, urls):
         else:
             urls.setdefault(component, url)
 
+    return errors
+
 
 def set_urls(request, urls):
     '''Build 'urls' dict with all the urls
     '''
-    home_url = url_of_page(request, 'home_page')
-    urls.setdefault('pgwui_home', home_url)
-    set_component_urls(request, urls)
+    errors = []
+    try:
+        home_url = url_of_page(request, 'home_page')
+    except ex.BadPathError as exp:
+        errors.append(exp)
+        # set_component_urls() requires a 'pgwui_home' key, and the error
+        # means the program will exit and the value never be used.
+        urls.setdefault('pgwui_home', None)
+    else:
+        urls.setdefault('pgwui_home', home_url)
+    errors.extend(set_component_urls(request, urls))
+    return errors
 
 
 def add_urls_setting(config, settings):
@@ -115,7 +129,8 @@ def add_urls_setting(config, settings):
     # be installed in a production enviornment.  And some RAM.
     request = pyramid.request.Request.blank('/')
     request.registry = config.registry
-    set_urls(request, urls)
+    errors = set_urls(request, urls)
     settings['pgwui']['urls'] = urls
     log.debug('Routing map of route names to url paths which is given'
               f' to the templates: {urls}')
+    return errors
diff --git a/tests/test_assets.py b/tests/test_assets.py
new file mode 100644 (file)
index 0000000..9650edf
--- /dev/null
@@ -0,0 +1,60 @@
+# Copyright (C) 2018, 2020 The Meme Factory, Inc.  http://www.karlpinc.com/
+
+# This file is part of PGWUI_Common.
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program 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
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public
+# License along with this program.  If not, see
+# <http://www.gnu.org/licenses/>.
+#
+
+# Karl O. Pinc <kop@karlpinc.com>
+
+import pytest
+
+import logging
+import pgwui_common.assets as assets
+
+from pgwui_testing import testing
+
+# Activiate the PGWUI pytest plugin
+pytest_plugins = ("pgwui",)
+
+# Mark all tests with "unittest"
+pytestmark = pytest.mark.unittest
+
+mock_override_asset = testing.instance_method_mock_fixture('override_asset')
+
+
+# override_asset()
+
+@pytest.mark.parametrize(
+    ('overrides', 'call_count'), [
+        ({}, 0),
+        ({'some_asset': 'new_asset'}, 1),
+        ({'some_asset': 'new_asset',
+          'other_asset': 'other new asset'}, 2)])
+def test_override_asset(
+        caplog, pyramid_config, mock_override_asset, overrides, call_count):
+    '''override_asset() is called for each override
+    '''
+    caplog.set_level(logging.DEBUG)
+    mocked_override_asset = mock_override_asset(pyramid_config)
+
+    if overrides:
+        settings = {'override_assets': overrides}
+    else:
+        settings = {}
+    assets.override_assets(pyramid_config, {'pgwui': settings})
+
+    assert mocked_override_asset.call_count == call_count
+    assert len(caplog.record_tuples) == call_count
index 1bc49135853bf7deebd46cb859d02665e291ad70..5c854ef41444b794321265f4dfdb991aad69a574 100644 (file)
@@ -183,7 +183,7 @@ mock_url_of_page = testing.make_mock_fixture(
 # set_menu_url()
 
 @pytest.mark.parametrize(
-    "test_urls,expected",
+    ('test_urls', 'expected_urls'),
     [
         # menu and home have identical urls, no url is added for menu
         ({'pgwui_menu': '/', 'pgwui_home': '/'},
@@ -194,10 +194,11 @@ mock_url_of_page = testing.make_mock_fixture(
         # menu and home have different urls, url is added for menu
         ({'pgwui_menu': '/menu', 'pgwui_home': '/'},
          {'pgwui_menu': '/menu'})])
-def test_set_menu_url(
+def test_set_menu_url_good_path(
         pyramid_request_config, mock_method_route_path, mock_url_of_page,
-        test_urls, expected):
-    '''The expected urls are returned
+        test_urls, expected_urls):
+    '''The expected urls are returned without errors when the path
+    settings are good, when the page's assets and routes have paths
     '''
     def path_func(name):
         return test_urls[name]
@@ -208,10 +209,28 @@ def test_set_menu_url(
     mocked_route_path.side_effect = path_func
 
     urls_dict = {'pgwui_home': test_urls['pgwui_home']}
-    expected.update(urls_dict)
-    urls.set_menu_url(request, urls_dict)
+    expected_urls.update(urls_dict)
+    result = urls.set_menu_url(request, urls_dict)
 
-    assert urls_dict == expected
+    assert urls_dict == expected_urls
+    assert result == []
+
+
+def test_set_menu_url_bad_page(
+        pyramid_request_config, mock_method_route_path, mock_url_of_page):
+    '''The expected urls are returned with errors  when the page settings
+    are bad, when the page's asset or route has no path
+    '''
+    expected_urls = {'home_page': '/', 'pgwui_logout': '/logout'}
+
+    mock_url_of_page.side_effect = common_ex.BadPathError
+    request = get_current_request
+    new_urls = expected_urls.copy()
+    result = urls.set_menu_url(request, new_urls)
+
+    assert new_urls == expected_urls
+    assert len(result) == 1
+    assert isinstance(result[0], common_ex.BadPathError)
 
 
 mock_set_menu_url = testing.make_mock_fixture(
@@ -235,20 +254,22 @@ def test_set_component_urls(
         pyramid_request_config, mock_method_route_path, mock_set_menu_url,
         mock_find_pgwui_components, test_urls):
     '''Urls are set for every component which has a route, except for
-    pgwui_menu
+    pgwui_menu, expected errors are returned
     '''
+    test_errors = ['some error']
     test_components = list(test_urls) + ['pgwui_noroute']
 
     def url_func(url):
         return test_urls[url]
 
+    mock_set_menu_url.return_value = test_errors
     request = get_current_request()
     mocked_route_path = mock_method_route_path(request)
     mocked_route_path.side_effect = url_func
     mock_find_pgwui_components.return_value = test_components
 
     urls_dict = dict()
-    urls.set_component_urls(request, urls_dict)
+    result = urls.set_component_urls(request, urls_dict)
 
     expected_urls = test_urls.copy()
     if 'pgwui_menu' in expected_urls:
@@ -256,6 +277,7 @@ def test_set_component_urls(
 
     mock_set_menu_url.assert_called_once()
     assert urls_dict == expected_urls
+    assert result == test_errors
 
 
 mock_set_component_urls = testing.make_mock_fixture(
@@ -264,20 +286,44 @@ mock_set_component_urls = testing.make_mock_fixture(
 
 # set_urls()
 
-def test_set_urls(
+def test_set_urls_good_path(
         pyramid_request_config, mock_url_of_page, mock_set_component_urls):
-    '''The 'home' url is added and set_component_urls() called
+    '''When the 'home_page' route path is good the 'home' url is added,
+    set_component_urls() called, and the expected errors are returned
     '''
+    component_errors = ['some error']
     test_home_route = '/'
     request = get_current_request()
 
+    mock_set_component_urls.return_value = component_errors
     mock_url_of_page.return_value = test_home_route
 
     urls_dict = dict()
-    urls.set_urls(request, urls_dict)
+    result = urls.set_urls(request, urls_dict)
 
     assert urls_dict['pgwui_home'] == test_home_route
     mock_set_component_urls.assert_called_once()
+    assert result == component_errors
+
+
+def test_set_urls_bad_path(
+        pyramid_request_config, mock_url_of_page, mock_set_component_urls):
+    '''When the 'home_page' route is bad a bad path error is one of the errors
+    returned
+    '''
+    component_errors = ['some error']
+    home_error = common_ex.BadPathError()
+    request = get_current_request()
+
+    mock_url_of_page.side_effect = home_error
+    mock_set_component_urls.return_value = component_errors
+
+    urls_dict = dict()
+    result = urls.set_urls(request, urls_dict)
+
+    assert 'pgwui_home' in urls_dict
+    mock_set_component_urls.assert_called_once()
+    assert home_error in result
 
 
 mock_set_urls = testing.make_mock_fixture(
@@ -292,10 +338,12 @@ def test_add_urls_setting(
     are put in the pgwui dict in the settings
     '''
     caplog.set_level(logging.DEBUG)
+    expected_errors = ['some error']
     expected_urls = {'key1': 'val1', 'key2': 'val2'}
 
     def set_urls(request, urls):
         urls.update(expected_urls)
+        return expected_errors
 
     mock_set_urls.side_effect = set_urls
 
@@ -304,12 +352,14 @@ def test_add_urls_setting(
     request.registry.settings = request_settings
 
     settings = {'pgwui': {}}
-    urls.add_urls_setting(pyramid_request_config, settings)
+    result = urls.add_urls_setting(pyramid_request_config, settings)
 
     mock_request_blank.blank.assert_called_once()
     mock_set_urls.assert_called_once()
     assert settings['pgwui']['urls'] == expected_urls
 
+    assert result == expected_errors
+
     logs = caplog.record_tuples
     assert len(logs) == 1
     assert logs[0][1] == logging.DEBUG