Switch mock handling in tests; plugin config declarations are ok
authorKarl O. Pinc <kop@karlpinc.com>
Sun, 28 Jun 2020 20:23:26 +0000 (15:23 -0500)
committerKarl O. Pinc <kop@karlpinc.com>
Sun, 28 Jun 2020 20:23:26 +0000 (15:23 -0500)
setup.py
src/pgwui_server/__init__.py
tests/test___init__.py
tox.ini

index f207711d585f7278bd5a7581439815173b212552..a9e94051193f4f508a39afdf8faf7527b4fe63e5 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -61,6 +61,7 @@ long_description = filter_readme()
 tests_require = [
     'pytest>=3.7.4',
     'pytest-cov',
+    'pgwui_testing==' + version,
 ]
 
 setup(
index a85140d06e3798b75caee1a909eb8f6fd50a9931..e6baaa73eb75cc5548d574f9df343b17ee680617 100644 (file)
 from ast import literal_eval
 from pyramid.config import Configurator
 import logging
-import pkg_resources
 import sys
 
+from pgwui_common import plugin
+
 # Constants
 
 # All the settings recognized by PGWUI
@@ -111,11 +112,12 @@ class HMACLengthError(BadHMACError):
 
 # Functions
 
-def abort_on_bad_setting(errors, key):
+def abort_on_bad_setting(errors, key, component_keys):
     '''Abort on a bad pgwui setting
     '''
     if key[:6] == 'pgwui.':
-        if key[6:] not in SETTINGS:
+        if (key[6:] not in SETTINGS
+                and key not in component_keys):
             errors.append(UnknownSettingKeyError(key))
 
 
@@ -187,11 +189,12 @@ def validate_literal_column_headings(errors, settings):
         errors.append(BadLiteralColumnHeadingsError(value))
 
 
-def validate_settings(errors, settings):
+def validate_settings(errors, settings, components):
     '''Be sure all settings validate
     '''
+    component_keys = ['pgwui.{}'.format(component) for component in components]
     for key in settings.keys():
-        abort_on_bad_setting(errors, key)
+        abort_on_bad_setting(errors, component_keys, key)
     validate_setting_values(errors, settings)
     validate_hmac(errors, settings)
 
@@ -211,11 +214,11 @@ def exit_reporting_errors(errors):
     sys.exit(1)
 
 
-def exit_on_invalid_settings(settings):
+def exit_on_invalid_settings(settings, components):
     '''Exit when settings don't validate
     '''
     errors = []
-    validate_settings(errors, settings)
+    validate_settings(errors, settings, components)
     if errors:
         exit_reporting_errors(errors)
 
@@ -245,14 +248,7 @@ def add_routes(config, settings):
             config.add_route(name, route)
 
 
-def find_pgwui_components():
-    '''Return list of all pgwui component names as strings
-    '''
-    return [entry_point.resolve().__name__ for entry_point in
-            pkg_resources.iter_entry_points('pgwui.components')]
-
-
-def autoconfig_components(settings):
+def autoconfigurable_components(settings, components):
     '''Automatic pgwui component discovery
     '''
     autoconfig = settings.get('pgwui.autoconfigure')
@@ -262,25 +258,31 @@ def autoconfig_components(settings):
     if 'pyramid.include' in settings:
         log.info(AutoconfigureConflict())
 
-    return find_pgwui_components()
+    return components
 
 
-def pgwui_server_config(settings):
-    '''Configure pyramid
+def apply_component_defaults(settings, components):
+    '''Apply component default settings to existing settings
     '''
-    exit_on_invalid_settings(settings)
-
-    components = autoconfig_components(settings)
+    components_to_config = autoconfigurable_components(settings, components)
 
     rp = settings.get('pgwui.route_prefix')
     with Configurator(settings=settings, route_prefix=rp) as config:
         config.include('pgwui_common')
-        for component in components:
+        for component in components_to_config:
             config.include(component)
         add_routes(config, settings)
     return config
 
 
+def pgwui_server_config(settings):
+    '''Configure pyramid
+    '''
+    components = plugin.find_pgwui_components()
+    exit_on_invalid_settings(settings, components)
+    return apply_component_defaults(settings, components)
+
+
 def main(global_config, **settings):
     '''Return a Pyramid WSGI application
     '''
index 9ceda1d0b012cb93a13e9b3d4e80bba1b1e17fca..90d81539ef28c8795d5814130fb8ba8484ca1d4b 100644 (file)
@@ -1,4 +1,5 @@
-# Copyright (C) 2018, 2019 The Meme Factory, Inc.  http://www.meme.com/
+# Copyright (C) 2018, 2019, 2020 The Meme Factory, Inc.
+# http://www.karlpinc.com/
 
 # This file is part of PGWUI_Server.
 #
 # <http://www.gnu.org/licenses/>.
 #
 
-# Karl O. Pinc <kop@meme.com>
+# Karl O. Pinc <kop@karlpinc.com>
 
 import logging
 import pytest
 import sys
 
+import pyramid.testing
+
+import pgwui_common.plugin
+# Use as a regular module, not a plugin, so lint checks work
+from pgwui_testing import testing
+
 import pgwui_server.__init__ as pgwui_server_init
 
 
@@ -35,6 +42,7 @@ TEST_SETTINGS = {
 
 
 # Use contextlib.AbstractContextManager for Python >= 3.6
+# (Or, better, use the magic mock maker that's not yet integrated.)
 class MockConfigurator():
     def __init__(self, **kwargs):
         pass
@@ -52,12 +60,10 @@ class MockConfigurator():
         pass
 
 
-class MockConfig():
-    def __init__(self):
-        self.add_route_called = 0
+mock_add_route = testing.instance_method_mock_fixture('add_route')
 
-    def add_route(self, *args):
-        self.add_route_called += 1
+mock_find_pgwui_components = testing.make_mock_fixture(
+    pgwui_common.plugin, 'find_pgwui_components')
 
 
 # Unit tests
@@ -67,7 +73,7 @@ class MockConfig():
 def test_abort_on_bad_setting_unknown():
     '''Nothing bad happens when there's a non-pgwui setting'''
     errors = []
-    pgwui_server_init.abort_on_bad_setting(errors, 'foo')
+    pgwui_server_init.abort_on_bad_setting(errors, 'foo', [])
 
     assert errors == []
 
@@ -75,7 +81,7 @@ def test_abort_on_bad_setting_unknown():
 def test_abort_on_bad_setting_bad():
     '''Delivers an error on a bad pgwui setting'''
     errors = []
-    pgwui_server_init.abort_on_bad_setting(errors, 'pgwui.foo')
+    pgwui_server_init.abort_on_bad_setting(errors, 'pgwui.foo', [])
 
     assert errors
     assert isinstance(errors[0], pgwui_server_init.UnknownSettingKeyError)
@@ -84,11 +90,24 @@ def test_abort_on_bad_setting_bad():
 def test_abort_on_bad_setting_good():
     '''Does nothing when a known pgwui setting is supplied'''
     errors = []
-    pgwui_server_init.abort_on_bad_setting(errors, 'pgwui.pg_host')
+    pgwui_server_init.abort_on_bad_setting(errors, 'pgwui.pg_host', [])
+
+    assert errors == []
+
+
+def test_abort_on_bad_setting_plugin():
+    '''Does nothing when a known plugin has a setting'''
+    errors = []
+    pgwui_server_init.abort_on_bad_setting(
+        errors, 'pgwui.pgwui_upload', ['pgwui.pgwui_upload'])
 
     assert errors == []
 
 
+mock_abort_on_bad_setting = testing.make_mock_fixture(
+    pgwui_server_init, 'abort_on_bad_setting')
+
+
 # require_setting()
 
 def test_require_setting_missing():
@@ -108,6 +127,10 @@ def test_require_setting_present():
     assert errors == []
 
 
+mock_require_setting = testing.make_mock_fixture(
+    pgwui_server_init, 'require_setting')
+
+
 # boolean_setting()
 
 def test_boolean_setting_missing():
@@ -143,30 +166,23 @@ def test_boolean_setting_notboolean():
     assert isinstance(errors[0], pgwui_server_init.NotBooleanSettingError)
 
 
+mock_boolean_setting = testing.make_mock_fixture(
+    pgwui_server_init, 'boolean_setting')
+
+
 # validate_setting_values()
 
-def test_validate_setting_values(monkeypatch):
+def test_validate_setting_values(mock_require_setting, mock_boolean_setting):
     '''Calls require_setting() and boolean_setting()'''
-    require_setting_called = False
-    boolean_setting_called = False
-
-    def mock_require_setting(*args):
-        nonlocal require_setting_called
-        require_setting_called = True
 
-    def mock_boolean_setting(*args):
-        nonlocal boolean_setting_called
-        boolean_setting_called = True
+    pgwui_server_init.validate_setting_values([], {})
 
-    monkeypatch.setattr(pgwui_server_init, 'require_setting',
-                        mock_require_setting)
-    monkeypatch.setattr(pgwui_server_init, 'boolean_setting',
-                        mock_boolean_setting)
+    assert mock_require_setting.called
+    assert mock_boolean_setting.called
 
-    pgwui_server_init.validate_setting_values([], {})
 
-    assert require_setting_called
-    assert boolean_setting_called
+mock_validate_setting_values = testing.make_mock_fixture(
+    pgwui_server_init, 'validate_setting_values')
 
 
 # do_validate_hmac()
@@ -190,22 +206,24 @@ def test_do_validate_hmac_False():
     assert result is False
 
 
+mock_do_validate_hmac = testing.make_mock_fixture(
+    pgwui_server_init, 'do_validate_hmac')
+
+
 # validate_hmac()
 
-def test_validate_hmac_unvalidated(monkeypatch):
+def test_validate_hmac_unvalidated(mock_do_validate_hmac):
     '''No error is returned when hmac validation is off'''
-    monkeypatch.setattr(pgwui_server_init, 'do_validate_hmac',
-                        lambda *args: False)
+    mock_do_validate_hmac.return_value = False
     errors = []
     pgwui_server_init.validate_hmac(errors, {})
 
     assert errors == []
 
 
-def test_validate_hmac_success(monkeypatch):
+def test_validate_hmac_success(mock_do_validate_hmac):
     '''No error is returned when hmac is validated an the right length'''
-    monkeypatch.setattr(pgwui_server_init, 'do_validate_hmac',
-                        lambda *args: True)
+    mock_do_validate_hmac.return_value = True
     errors = []
     pgwui_server_init.validate_hmac(
         errors, {'session.secret': 'x' * pgwui_server_init.HMAC_LEN})
@@ -213,10 +231,9 @@ def test_validate_hmac_success(monkeypatch):
     assert errors == []
 
 
-def test_validate_hmac_missing(monkeypatch):
+def test_validate_hmac_missing(mock_do_validate_hmac):
     '''Deliver error when hmac is validated and missing'''
-    monkeypatch.setattr(pgwui_server_init, 'do_validate_hmac',
-                        lambda *args: True)
+    mock_do_validate_hmac.return_value = True
     errors = []
     pgwui_server_init.validate_hmac(errors, {})
 
@@ -224,10 +241,9 @@ def test_validate_hmac_missing(monkeypatch):
     assert isinstance(errors[0], pgwui_server_init.NoHMACError)
 
 
-def test_validate_hmac_length(monkeypatch):
+def test_validate_hmac_length(mock_do_validate_hmac):
     '''Deliver error when hmac is validated and the wrong length'''
-    monkeypatch.setattr(pgwui_server_init, 'do_validate_hmac',
-                        lambda *args: True)
+    mock_do_validate_hmac.return_value = True
     errors = []
     pgwui_server_init.validate_hmac(errors, {'session.secret': ''})
 
@@ -235,6 +251,10 @@ def test_validate_hmac_length(monkeypatch):
     assert isinstance(errors[0], pgwui_server_init.HMACLengthError)
 
 
+mock_validate_hmac = testing.make_mock_fixture(
+    pgwui_server_init, 'validate_hmac')
+
+
 # validate_literal_column_headings()
 
 def test_validate_literal_column_headings_nosetting():
@@ -283,29 +303,30 @@ def test_validate_literal_column_headings_bad():
         errors[0], pgwui_server_init.BadLiteralColumnHeadingsError)
 
 
+mock_validate_literal_column_headings = testing.make_mock_fixture(
+    pgwui_server_init, 'validate_literal_column_headings')
+
+
 # validate_settings()
 
-def test_validate_settings(monkeypatch):
+def test_validate_settings(mock_abort_on_bad_setting,
+                           mock_validate_setting_values,
+                           mock_validate_hmac):
     '''Calls abort_on_bad_setting() for each key in setting
     '''
-    count = 0
-
-    def mock_abort_on_bad_setting(*args):
-        nonlocal count
-        count += 1
-
-    monkeypatch.setattr(pgwui_server_init, 'abort_on_bad_setting',
-                        mock_abort_on_bad_setting)
-    monkeypatch.setattr(pgwui_server_init, 'validate_setting_values',
-                        lambda *args: None)
-    monkeypatch.setattr(pgwui_server_init, 'validate_hmac',
-                        lambda *args: None)
     settings = {'key1': 'value1',
                 'key2': 'value2'}
 
     errors = []
-    pgwui_server_init.validate_settings(errors, settings)
-    assert count == len(settings)
+    pgwui_server_init.validate_settings(errors, settings, [])
+
+    assert mock_validate_setting_values.called
+    assert mock_validate_hmac.called
+    assert mock_abort_on_bad_setting.call_count == len(settings)
+
+
+mock_validate_settings = testing.make_mock_fixture(
+    pgwui_server_init, 'validate_settings')
 
 
 # exit_reporting_errors()
@@ -364,43 +385,38 @@ def test_exit_reporting_errors_printed(
     assert errlines[1] != 'three'
 
 
+mock_exit_reporting_errors = testing.make_mock_fixture(
+    pgwui_server_init, 'exit_reporting_errors')
+
+
 # exit_on_invalid_settings()
 
-def test_exit_on_invalid_settings_invalid(monkeypatch):
+def test_exit_on_invalid_settings_invalid(monkeypatch,
+                                          mock_exit_reporting_errors):
     '''Calls validate_settings and exit_reporting_errors() when
     setting is invalid
     '''
-    def mock_validate_settings(errors, settings):
+    def mock_validate_settings(errors, settings, components):
         errors.append('error1')
 
-    exit_reporting_errors_called = False
-
-    def mock_exit_reporting_errors(errors):
-        nonlocal exit_reporting_errors_called
-        exit_reporting_errors_called = True
-
     monkeypatch.setattr(pgwui_server_init, 'validate_settings',
                         mock_validate_settings)
-    monkeypatch.setattr(pgwui_server_init, 'exit_reporting_errors',
-                        mock_exit_reporting_errors)
 
-    pgwui_server_init.exit_on_invalid_settings({})
+    pgwui_server_init.exit_on_invalid_settings({}, [])
 
-    assert exit_reporting_errors_called
+    assert mock_exit_reporting_errors.called
 
 
-def test_exit_on_invalid_settings_valid(monkeypatch):
+def test_exit_on_invalid_settings_valid(mock_validate_settings):
     '''Returns, without exiting, when all settings are valid
     '''
-    def mock_validate_settings(errors, settings):
-        pass
+    pgwui_server_init.exit_on_invalid_settings({}, [])
 
-    monkeypatch.setattr(pgwui_server_init, 'validate_settings',
-                        mock_validate_settings)
+    assert True
 
-    pgwui_server_init.exit_on_invalid_settings({})
 
-    assert True
+mock_exit_on_invalid_settings = testing.make_mock_fixture(
+    pgwui_server_init, 'exit_on_invalid_settings')
 
 
 # parse_assignments()
@@ -431,55 +447,29 @@ def test_parse_assignments_dict():
                                ])
 
 
-# find_pgwui_components()
-
-def test_find_pgwui_components(monkeypatch):
-    '''Returns list of entry points via iter_entry_points()
-    '''
-    entry_points = ['a', 'b', 'c']
-
-    class MockEntryPoint():
-        def __init__(self, val):
-            self.__name__ = val
-
-        def resolve(self):
-            return self
-
-    class MockPkgResources():
-        def iter_entry_points(*args):
-            return [MockEntryPoint(name) for name in entry_points]
+mock_parse_assignments = testing.make_mock_fixture(
+    pgwui_server_init, 'parse_assignments')
 
-    monkeypatch.setattr(
-        pgwui_server_init, 'pkg_resources', MockPkgResources())
 
-    result = pgwui_server_init.find_pgwui_components()
+# autoconfigurable_components()
 
-    assert result == entry_points
-
-
-# autoconfig_components()
-
-def test_autoconfig_components_no_autoconfig(monkeypatch):
+def test_autoconfiguable_components_no_autoconfig():
     '''When the settings have no pgwui.autoconfigure return an empty list
     '''
-    monkeypatch.setattr(pgwui_server_init, 'find_pgwui_components',
-                        lambda *args: [])
+    test_components = ['some', 'components']
 
-    result = pgwui_server_init.autoconfig_components({})
+    result = pgwui_server_init.autoconfigurable_components({}, test_components)
 
     assert result == []
 
 
-def test_autoconfig_components_log_info(monkeypatch, caplog):
+def test_autoconfigurable_components_log_info(caplog):
     '''When pyramid.include is in the settings an INFO message is logged
     '''
-    monkeypatch.setattr(pgwui_server_init, 'find_pgwui_components',
-                        lambda *args: [])
-
     caplog.set_level(logging.INFO)
 
-    pgwui_server_init.autoconfig_components({'pgwui.autoconfigure': True,
-                                             'pyramid.include': None})
+    pgwui_server_init.autoconfigurable_components(
+        {'pgwui.autoconfigure': True, 'pyramid.include': None}, [])
 
     logs = caplog.record_tuples
 
@@ -489,61 +479,82 @@ def test_autoconfig_components_log_info(monkeypatch, caplog):
     assert level == logging.INFO
 
 
-def test_autoconfig_components_find_pgwui_components_called(monkeypatch):
-    '''When pyramid.include is in the settings an INFO message is logged
+def test_autoconfigurable_components_components_returned():
+    '''The suppiled components are returned when autoconfigure is True
     '''
-    find_pgwui_components_called = False
+    test_components = ['some', 'components']
 
-    def mock_find_pgwui_components(*args):
-        nonlocal find_pgwui_components_called
-        find_pgwui_components_called = True
+    result = pgwui_server_init.autoconfigurable_components(
+        {'pgwui.autoconfigure': True}, test_components)
 
-    monkeypatch.setattr(pgwui_server_init, 'find_pgwui_components',
-                        mock_find_pgwui_components)
+    assert result == test_components
 
-    pgwui_server_init.autoconfig_components({'pgwui.autoconfigure': True,
-                                             'mock_pgwui_component': 'foo'})
 
-    assert find_pgwui_components_called
+mock_autoconfigurable_components = testing.make_mock_fixture(
+    pgwui_server_init, 'autoconfigurable_components')
 
 
 # add_routes()
 
-def test_add_routes_empty():
+def test_add_routes_empty(mock_add_route):
     '''When there is no pgwui.routes setting nothing gets added'''
-    config = MockConfig()
-    pgwui_server_init.add_routes(config, {})
-    assert config.add_route_called == 0
+    with pyramid.testing.testConfig() as config:
+        mocked_add_route = mock_add_route(config)
+        pgwui_server_init.add_routes(config, {})
+
+    assert not mocked_add_route.called
 
 
-def test_add_routes_notempty(monkeypatch):
+def test_add_routes_notempty(mock_add_route, mock_parse_assignments):
     '''When there is a pgwui.routes setting config.add_route() is called
     for each route'''
-    config = MockConfig()
-    monkeypatch.setattr(pgwui_server_init, 'parse_assignments',
-                        lambda *args: [('name1', 'route1'),
-                                       ('name2', 'route2')])
-    pgwui_server_init.add_routes(config, {'pgwui.routes': ''})
-    assert config.add_route_called == 2
+    test_routes = [('name1', 'route1'),
+                   ('name2', 'route2')]
+    mock_parse_assignments.return_value = test_routes
+    with pyramid.testing.testConfig() as config:
+        mocked_add_route = mock_add_route(config)
+        pgwui_server_init.add_routes(config, {'pgwui.routes': ''})
 
+    assert mocked_add_route.call_count == len(test_routes)
 
-# pgwui_server_config()
 
-def test_pgwui_server_config(monkeypatch):
-    '''Returns a configuration'''
-    monkeypatch.setattr(pgwui_server_init, 'exit_on_invalid_settings',
-                        lambda *args: True)
-    monkeypatch.setattr(pgwui_server_init, 'autoconfig_components',
-                        lambda *args: ['pgwui_mock_component_name'])
+mock_add_routes = testing.make_mock_fixture(
+    pgwui_server_init, 'add_routes')
+
+
+# apply_component_defaults()
+
+
+def test_apply_component_defaults(monkeypatch,
+                                  mock_autoconfigurable_components,
+                                  mock_add_routes):
+    mock_autoconfigurable_components.return_value = \
+        ['pgwui_mock_component_name']
     monkeypatch.setattr(pgwui_server_init, 'Configurator',
                         MockConfigurator)
-    monkeypatch.setattr(pgwui_server_init, 'add_routes',
-                        lambda *args: None)
 
-    result = pgwui_server_init.pgwui_server_config({})
+    result = pgwui_server_init.apply_component_defaults({}, [])
     assert isinstance(result, MockConfigurator)
 
 
+mock_apply_component_defaults = testing.make_mock_fixture(
+    pgwui_server_init, 'apply_component_defaults')
+
+
+# pgwui_server_config()
+
+def test_pgwui_server_config(
+        mock_find_pgwui_components,
+        mock_apply_component_defaults, mock_exit_on_invalid_settings):
+    '''Returns a configuration'''
+    test_configurator = 'test configurator'
+    mock_apply_component_defaults.return_value = test_configurator
+
+    result = pgwui_server_init.pgwui_server_config({})
+
+    assert result == test_configurator
+
+
 # main()
 def test_main(monkeypatch):
     '''Returns a wsgi app'''
diff --git a/tox.ini b/tox.ini
index 809a13005e301e3e65cf19819a0bad0834a094f6..7c02bc4ae6a414f780a1a00c5293dfa6a8dc22fc 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -15,6 +15,9 @@ deps =
     pytest-cov
     twine
     # coverage
+    # This is a bug, because we'd like to specify the pgwui_testing
+    # version to be equal to the pgwui_server version being tested.
+    pgwui_testing
 commands =
     check-manifest
     python setup.py sdist