Require some settings, supply defaults for others, validate setting values
authorKarl O. Pinc <kop@meme.com>
Sat, 17 Nov 2018 09:08:08 +0000 (03:08 -0600)
committerKarl O. Pinc <kop@meme.com>
Sat, 17 Nov 2018 09:08:08 +0000 (03:08 -0600)
examples/development.ini
examples/pgwui.ini
src/pgwui_server/__init__.py
tests/test___init__.py

index 0af2f3a10423c06cd00675600734ad8255df8f79..c7b14a18129a7d17b48f43fb9c4b3d4cba156e75 100644 (file)
@@ -16,11 +16,13 @@ use = egg:PGWUI_Server
 
 # Postgres client configuration.
 
-# (An empty string for host means use the unix socket.)
+# Both pgwui.pg_host and pgwui.pg_port are optional;
+# they default to the Unix PG socket and the default Postgres port.
+# An empty string for host means use the unix socket.
 pgwui.pg_host =
 pgwui.pg_port = 5432
 
-# There are occasions when PGWUI uses a default database.
+# There are occasions when PGWUI uses a default database.  (optional)
 pgwui.default_db = template1
 
 # What PGWUI modules to use.
@@ -29,7 +31,7 @@ pyramid.includes =
     pgwui_upload
     pyramid_debugtoolbar
 
-# Whether or not to change the db content.
+# Whether or not to change the db content.  (required)
 pgwui.dry_run = False
 
 # Routing
@@ -46,7 +48,7 @@ pgwui.dry_run = False
 # For more information on route syntax see:
 # https://docs.pylonsproject.org/projects/pyramid/en/master/narr/urldispatch.html#route-pattern-syntax
 
-# A prefix for all routes.  If the prefix is "/a/b/c" then
+# A prefix for all routes.  (optional)  If the prefix is "/a/b/c" then
 # all URLs will begin with something like: http://example.com/a/b/c
 # The default is no prefix.
 # pgwui.route_prefix =
@@ -58,6 +60,8 @@ pgwui.dry_run = False
 # the component.  So to access the logout page at
 # http://example.com/logmeout the line would be: logout = /logmeout
 #
+# Overriding routes is optional.
+#
 # The default for some PGWUI components are:
 # pgwui.routes =
 #   logout = /logout
@@ -65,7 +69,7 @@ pgwui.dry_run = False
 
 # Settings validation
 
-# Whether or not to validate the session.secret setting.
+# Whether or not to validate the session.secret setting.  (optional)
 # session.secret must be valid to detect Cross-Site Request Forgery (CSRF)
 # vulnerabilties.  Validation is on by default.
 pgwui.validate_hmac = False
index 8edda38e210bb8a172fa3180e43a641bc28e30d1..87010671c38e4e36223ed20fe6a6119e8c0237bf 100644 (file)
@@ -16,11 +16,13 @@ use = egg:PGWUI_Server
 
 # Postgres client configuration.
 
-# (An empty string for host means use the unix socket.)
+# Both pgwui.pg_host and pgwui.pg_port are optional;
+# they default to the Unix PG socket and the default Postgres port.
+# An empty string for host means use the unix socket.
 pgwui.pg_host =
 pgwui.pg_port = 5432
 
-# There are occasions when PGWUI uses a default database.
+# There are occasions when PGWUI uses a default database.  (optional)
 pgwui.default_db = template1
 
 # What PGWUI modules to use.
@@ -28,7 +30,7 @@ pyramid.includes =
     pgwui_logout
     pgwui_upload
 
-# Whether or not to change the db content.
+# Whether or not to change the db content.  (required)
 pgwui.dry_run = False
 
 # Routing
@@ -45,7 +47,7 @@ pgwui.dry_run = False
 # For more information on route syntax see:
 # https://docs.pylonsproject.org/projects/pyramid/en/master/narr/urldispatch.html#route-pattern-syntax
 
-# A prefix for all routes.  If the prefix is "/a/b/c" then
+# A prefix for all routes.  (optional)  If the prefix is "/a/b/c" then
 # all URLs will begin with something like: http://example.com/a/b/c
 # The default is no prefix.
 # pgwui.route_prefix =
@@ -57,16 +59,16 @@ pgwui.dry_run = False
 # the component.  So to access the logout page at
 # http://example.com/logmeout the line would be: logout = /logmeout
 #
+# Overriding routes is optional.
+#
 # The default for some PGWUI components are:
 # pgwui.routes =
 #   logout = /logmeout
 #   upload = /put-in
 
-
-
 # Settings validation
 
-# Whether or not to validate the session.secret setting.
+# Whether or not to validate the session.secret setting.  (optional)
 # session.secret must be valid to detect Cross-Site Request Forgery (CSRF)
 # vulnerabilties.  Validation is on by default.
 # pgwui.validate_hmac = True
index eb8ce80e1699ebfec211c22239593be38e708d1f..57cbbb6e7bae12e6180f511389748f3b02974fc8 100644 (file)
@@ -54,6 +54,18 @@ class UnknownSettingKeyError(Error):
         super().__init__('Unknown PGWUI setting: {}'.format(key))
 
 
+class MissingSettingError(Error):
+    def __init__(self, key):
+        super().__init__('Missing PGWUI setting: {}'.format(key))
+
+
+class NotBooleanSettingError(Error):
+    def __init__(self, key, value):
+        super().__init__(
+            'The "{}" PGWUI setting must be "True" or "False"'
+            .format(key))
+
+
 class BadHMACError(Error):
     pass
 
@@ -80,6 +92,46 @@ def abort_on_bad_setting(key):
             raise UnknownSettingKeyError(key)
 
 
+def require_setting(setting, settings):
+    if setting not in settings:
+        raise MissingSettingError(setting)
+
+
+def boolean_setting(setting, settings):
+    if setting in settings:
+        val = literal_eval(settings[setting])
+        if (val is not True
+                and val is not False):
+            raise NotBooleanSettingError(setting, settings[setting])
+
+
+def validate_setting_values(settings):
+    '''Check each settings value for validity
+    '''
+    # pg_host can be missing, it defaults to the Unix socket (in psycopg2)
+
+    # pg_port can be missing, it defaults to 5432 (in psycopg2)
+
+    # default_db can be missing, then the user sees no default
+
+    # dry_run
+    require_setting('pgwui.dry_run', settings)
+    boolean_setting('pgwui.dry_run', settings)
+
+    # route_prefix can be missing, defaults to no route prefix which is fine.
+
+    # routes can be missing, sensible defaults are built-in.
+
+    # validate_hmac
+    boolean_setting('pgwui.validate_hmac', settings)
+
+
+def supply_default_settings(settings):
+    '''Supply sensible defaults for omitted settings.
+    '''
+    settings['pgwui.default_db'] = ''
+
+
 def do_validate_hmac(settings):
     '''True unless the user has specificly rejected hmac validation
     '''
@@ -104,6 +156,8 @@ def validate_settings(settings):
     '''
     for key in settings.keys():
         abort_on_bad_setting(key)
+    validate_setting_values(settings)
+    supply_default_settings(settings)
     validate_hmac(settings)
 
 
index 09aa885a687d4462f5c682c03b8b164dcea62d1f..469989d8a63e7634cd8bb97acbbd5970e781a8c2 100644 (file)
@@ -27,6 +27,7 @@ import pgwui_server.__init__ as pgwui_server_init
 
 TEST_SETTINGS = {
     'pgwui.validate_hmac': 'False',
+    'pgwui.dry_run': 'False',
 }
 
 
@@ -76,6 +77,78 @@ def test_abort_on_bad_setting_good():
     pgwui_server_init.abort_on_bad_setting('pgwui.pg_host')
 
 
+# require_setting()
+
+def test_require_setting_missing():
+    '''Raise an exception when a required setting is missing'''
+    with pytest.raises(pgwui_server_init.MissingSettingError):
+        pgwui_server_init.require_setting('key', {})
+
+
+def test_require_setting_present():
+    '''Does nothing when a required setting is present'''
+    pgwui_server_init.require_setting('key',
+                                      {'key': 'value'})
+
+
+# boolean_setting()
+
+def test_boolean_setting_missing():
+    '''Does nothing when the setting is not in the settings'''
+    pgwui_server_init.boolean_setting('key', {})
+
+
+def test_boolean_setting_true():
+    '''Does nothing when the setting is "True"'''
+    pgwui_server_init.boolean_setting('key',
+                                      {'key': 'True'})
+
+
+def test_boolean_setting_false():
+    '''Does nothing when the setting is "False"'''
+    pgwui_server_init.boolean_setting('key',
+                                      {'key': 'False'})
+
+
+def test_boolean_setting_notboolean():
+    '''Raise an exception when the setting does not evaluate to a boolean'''
+    with pytest.raises(pgwui_server_init.NotBooleanSettingError):
+        pgwui_server_init.boolean_setting('key',
+                                          {'key': '0'})
+
+
+# validate_setting_values()
+
+def test_validate_setting_values(monkeypatch):
+    '''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
+
+    monkeypatch.setattr(pgwui_server_init, 'require_setting',
+                        mock_require_setting)
+    monkeypatch.setattr(pgwui_server_init, 'boolean_setting',
+                        mock_boolean_setting)
+
+    pgwui_server_init.validate_setting_values({})
+
+    assert require_setting_called
+    assert boolean_setting_called
+
+
+# supply_default_settings()
+def test_supply_default_settings():
+    '''Something is changed in the supplied settings'''
+    assert {} != pgwui_server_init.supply_default_settings({})
+
+
 # do_validate_hmac()
 
 def test_do_validate_hmac_none():
@@ -143,6 +216,10 @@ def test_validate_settings(monkeypatch):
 
     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, 'supply_default_settings',
+                        lambda arg: arg)
     monkeypatch.setattr(pgwui_server_init, 'validate_hmac',
                         lambda *args: None)
     settings = {'key1': 'value1',
@@ -232,7 +309,20 @@ def test_main_integrated():
 
 
 # Functional tests
+
 def test_unknownsettingkeyerror():
     '''Takes an argument'''
     assert isinstance(pgwui_server_init.UnknownSettingKeyError('key'),
-                      Exception)
+                      pgwui_server_init.Error)
+
+
+def test_missingsettingerror():
+    '''Takes an argument'''
+    assert isinstance(pgwui_server_init.MissingSettingError('key'),
+                      pgwui_server_init.Error)
+
+
+def test_notbooleansettingerror():
+    '''Takes two arguments'''
+    assert isinstance(pgwui_server_init.NotBooleanSettingError('key', 'val'),
+                      pgwui_server_init.Error)