--- /dev/null
+# Copyright (C) 2018, 2019, 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>
+
+'''Validate PGWUI_Core and PGWUI_Common configuration
+'''
+
+import re
+from ast import literal_eval
+
+from . import constants
+from . import exceptions as ex
+from . import checkset
+
+
+# Regular expressions for page "source" values, by type
+URL_RE = re.compile('^(?:(?:[^:/]+:)?//[^/])|(?:/(?:[^/]|$))')
+
+
+def key_to_ini(key):
+ '''Convert the setting key to a key used in an ini file's declaration
+ '''
+ return 'pgwui:{}'.format(key)
+
+
+def require_setting(errors, setting, pgwui_settings, formatter):
+ if setting not in pgwui_settings:
+ errors.append(ex.MissingSettingError(formatter(setting)))
+ return False
+ return True
+
+
+def boolean_setting(errors, setting, pgwui_settings):
+ if setting in pgwui_settings:
+ try:
+ val = literal_eval(pgwui_settings[setting])
+ except ValueError:
+ val = None
+ if (val is not True
+ and val is not False):
+ errors.append(ex.NotBooleanSettingError(
+ key_to_ini(setting), pgwui_settings[setting]))
+
+
+def validate_setting_values(errors, settings):
+ '''Check each settings value for validity
+ '''
+ pgwui_settings = settings['pgwui']
+
+ # 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(errors, 'dry_run', pgwui_settings, key_to_ini)
+ boolean_setting(errors, 'dry_run', pgwui_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(errors, 'validate_hmac', pgwui_settings)
+
+
+def do_validate_hmac(settings):
+ '''True unless the user has specificly rejected hmac validation
+ '''
+ pgwui_settings = settings['pgwui']
+ return ('validate_hmac' not in pgwui_settings
+ or literal_eval(pgwui_settings['validate_hmac']))
+
+
+def validate_hmac(errors, settings):
+ '''Unless otherwise requested, validate the session.secret setting'''
+ if not do_validate_hmac(settings):
+ return
+
+ if 'session.secret' not in settings:
+ errors.append(ex.NoHMACError())
+ return
+
+ if len(settings['session.secret']) != constants.HMAC_LEN:
+ errors.append(ex.HMACLengthError())
+ return
+
+
+def page_key_to_ini(page_key, subkey):
+ '''Convert the page setting subkey to a ini file declaration
+ '''
+ return key_to_ini(f'{page_key}:{subkey}')
+
+
+def require_page_settings(errors, required_settings, page_settings, page_key):
+ '''Check for required keys in the page setting
+ '''
+ def subkey_to_ini(subkey):
+ return page_key_to_ini(page_key, subkey)
+
+ have_settings = True
+ for subkey in required_settings:
+ have_settings &= require_setting(
+ errors, subkey, page_settings, subkey_to_ini)
+
+ return have_settings
+
+
+def validate_url_source(errors, page_key, source):
+ '''Validate the page setting "source" for URLs
+ '''
+ if URL_RE.match(source):
+ return
+ errors.append(ex.BadURLSourceError(
+ page_key_to_ini(page_key, 'source'), source))
+
+
+def validate_url_path(errors, page_key, page_settings):
+ '''Validate the page setting "url_path"
+ '''
+ url_path = page_settings['url_path']
+ if url_path[0:1] == '/':
+ return
+ errors.append(ex.BadFileURLPathError(
+ page_key_to_ini(page_key, 'url_path'), url_path))
+
+
+def validate_file_source(errors, page_key, source):
+ '''Validate the page setting "source" for files
+ '''
+ if source[0:1] == '/':
+ return
+ errors.append(ex.BadFileSourceError(
+ page_key_to_ini(page_key, 'file'), source))
+
+
+def validate_route_source(errors, page_key, source):
+ '''Validate the page setting "source" for routes
+
+ The routes are not yet established, so we don't confirm
+ existance at this point.
+ '''
+ if source != '':
+ return
+ errors.append(ex.BadRouteSourceError(
+ page_key_to_ini(page_key, 'route'), source))
+
+
+def validate_asset_source(errors, page_key, source):
+ '''Validate the page setting "source" for assets
+ '''
+ if source != '':
+ return
+ errors.append(ex.BadAssetSourceError(
+ page_key_to_ini(page_key, 'asset'), source))
+
+
+def validate_file_content(errors, page_key, page_settings, source):
+ '''Validate the content of a "file" page setting
+ '''
+ validate_file_source(errors, page_key, source)
+ if require_page_settings(
+ errors, ['url_path'], page_settings, page_key):
+ validate_url_path(errors, page_key, page_settings)
+ errors.extend(checkset.unknown_settings(
+ f'pgwui:{page_key}', ['type', 'source', 'url_path'], page_settings))
+
+
+def validate_type_content(errors, page_key, page_settings):
+ '''Validate the page setting's "type", and other page setting content
+ based on the type
+ '''
+ type = page_settings['type']
+ source = page_settings['source']
+ if type == 'URL':
+ validate_url_source(errors, page_key, source)
+ errors.extend(checkset.unknown_settings(
+ 'pgwui_common', ['type', 'source'], page_settings))
+ return
+ if type == 'file':
+ validate_file_content(errors, page_key, page_settings, source)
+ return
+ if type == 'route':
+ validate_route_source(errors, page_key, source)
+ errors.extend(checkset.unknown_settings(
+ 'pgwui_common', ['type', 'source'], page_settings))
+ return
+ if type == 'asset':
+ validate_asset_source(errors, page_key, source)
+ errors.extend(checkset.unknown_settings(
+ 'pgwui_common', ['type', 'source'], page_settings))
+ return
+
+ errors.append(ex.BadPageTypeError(
+ page_key_to_ini(page_key, 'type'), type))
+
+
+def validate_page_setting(errors, settings, page_key):
+ '''Validate the multiple values of the page setting
+ '''
+ pgwui_settings = settings['pgwui']
+ if page_key not in pgwui_settings:
+ return
+
+ page_settings = pgwui_settings[page_key]
+ if not require_page_settings(
+ errors, ['type', 'source'], page_settings, page_key):
+ return
+
+ validate_type_content(errors, page_key, page_settings)
+
+
+def validate_settings(errors, settings):
+ '''Validate all core settings
+ '''
+ validate_setting_values(errors, settings)
+ validate_hmac(errors, settings)
+ validate_page_setting(errors, settings, 'home_page')
+ validate_page_setting(errors, settings, 'menu_page')
--- /dev/null
+# Copyright (C) 2018, 2019, 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 pgwui_common.exceptions as ex
+import pgwui_common
+from pgwui_common import check_settings
+from pgwui_common import constants
+from pgwui_testing import testing
+
+# Mark all tests as unit tests
+pytestmark = pytest.mark.unittest
+
+mock_unknown_settings = testing.make_mock_fixture(
+ pgwui_common.checkset, 'unknown_settings')
+
+
+# key_to_ini()
+
+def test_key_to_ini():
+ '''The return value is as expected
+ '''
+ key = 'pgwui_example'
+ result = check_settings.key_to_ini(key)
+
+ assert result == 'pgwui:' + key
+
+
+mock_key_to_ini = testing.make_mock_fixture(
+ check_settings, 'key_to_ini')
+
+
+# require_setting()
+
+def test_require_setting_missing():
+ '''Deliver exception when a required setting is missing'''
+ errors = []
+ check_settings.require_setting(errors, 'key', {}, lambda x: x)
+
+ assert errors
+ assert isinstance(errors[0], ex.MissingSettingError)
+
+
+def test_require_setting_present():
+ '''Does nothing when a required setting is present'''
+ errors = []
+ check_settings.require_setting(
+ errors, 'key', {'key': 'value'}, lambda x: x)
+
+ assert errors == []
+
+
+mock_require_setting = testing.make_mock_fixture(
+ check_settings, 'require_setting')
+
+
+# boolean_setting()
+
+def test_boolean_setting_missing():
+ '''Does nothing when the setting is not in the settings'''
+ errors = []
+ check_settings.boolean_setting(errors, 'key', {})
+
+ assert errors == []
+
+
+def test_boolean_setting_true():
+ '''Does nothing when the setting is "True"'''
+ errors = []
+ check_settings.boolean_setting(errors, 'key', {'key': 'True'})
+
+ assert errors == []
+
+
+def test_boolean_setting_false():
+ '''Does nothing when the setting is "False"'''
+ errors = []
+ check_settings.boolean_setting(errors, 'key', {'key': 'False'})
+
+ assert errors == []
+
+
+def test_boolean_setting_notboolean():
+ '''Deliver an exception when the setting does not evaluate to a boolean'''
+ errors = []
+ check_settings.boolean_setting(errors, 'key', {'key': '0'})
+
+ assert errors
+ assert isinstance(errors[0], ex.NotBooleanSettingError)
+
+
+def test_boolean_setting_notparsable():
+ '''Deliver an exception when the setting does not evaluate to a
+ boolean because it is not parseable
+ '''
+ errors = []
+ check_settings.boolean_setting(errors, 'key', {'key': 'a'})
+
+ assert errors
+ assert isinstance(errors[0], ex.NotBooleanSettingError)
+
+
+mock_boolean_setting = testing.make_mock_fixture(
+ check_settings, 'boolean_setting')
+
+
+# validate_setting_values()
+
+def test_validate_setting_values(mock_require_setting, mock_boolean_setting):
+ '''Calls require_setting() and boolean_setting()'''
+
+ check_settings.validate_setting_values([], {'pgwui': {}})
+
+ assert mock_require_setting.called
+ assert mock_boolean_setting.called
+
+
+mock_validate_setting_values = testing.make_mock_fixture(
+ check_settings, 'validate_setting_values')
+
+
+# do_validate_hmac()
+
+def test_do_validate_hmac_none():
+ '''pgwui.validate_hmac defaults to True'''
+ assert check_settings.do_validate_hmac({'pgwui': {}}) is True
+
+
+def test_do_validate_hmac_True():
+ '''Require hmac validation when pgwui.validate_hmac is True'''
+ result = check_settings.do_validate_hmac(
+ {'pgwui': {'validate_hmac': 'True'}})
+ assert result is True
+
+
+def test_do_validate_hmac_False():
+ '''No hmac validation when pgwui.validate_hmac is False'''
+ result = check_settings.do_validate_hmac(
+ {'pgwui': {'validate_hmac': 'False'}})
+ assert result is False
+
+
+mock_do_validate_hmac = testing.make_mock_fixture(
+ check_settings, 'do_validate_hmac')
+
+
+# validate_hmac()
+
+def test_validate_hmac_unvalidated(mock_do_validate_hmac):
+ '''No error is returned when hmac validation is off'''
+ mock_do_validate_hmac.return_value = False
+ errors = []
+ check_settings.validate_hmac(errors, {})
+
+ assert errors == []
+
+
+def test_validate_hmac_success(mock_do_validate_hmac):
+ '''No error is returned when hmac is validated an the right length'''
+ mock_do_validate_hmac.return_value = True
+ errors = []
+ check_settings.validate_hmac(
+ errors, {'session.secret': 'x' * constants.HMAC_LEN})
+
+ assert errors == []
+
+
+def test_validate_hmac_missing(mock_do_validate_hmac):
+ '''Deliver error when hmac is validated and missing'''
+ mock_do_validate_hmac.return_value = True
+ errors = []
+ check_settings.validate_hmac(errors, {})
+
+ assert errors
+ assert isinstance(errors[0], ex.NoHMACError)
+
+
+def test_validate_hmac_length(mock_do_validate_hmac):
+ '''Deliver error when hmac is validated and the wrong length'''
+ mock_do_validate_hmac.return_value = True
+ errors = []
+ check_settings.validate_hmac(errors, {'session.secret': ''})
+
+ assert errors
+ assert isinstance(errors[0], ex.HMACLengthError)
+
+
+mock_validate_hmac = testing.make_mock_fixture(
+ check_settings, 'validate_hmac')
+
+
+# page_key_to_ini()
+
+def test_page_key_to_ini(mock_key_to_ini):
+ '''key_to_ini() is called, expected result returned
+ '''
+ mock_key_to_ini.return_value = 'foo'
+ result = check_settings.page_key_to_ini(None, None)
+ assert result == 'foo'
+
+
+mock_page_key_to_ini = testing.make_mock_fixture(
+ check_settings, 'page_key_to_ini')
+
+
+# require_page_settings()
+
+@pytest.mark.parametrize(
+ ('required_settings', 'rs_results', 'expected'), [
+ # Settings exist, return True
+ (['s1', 's2'], [True, True], True),
+ # One setting does not exist, return False
+ (['s1', 's2'], [True, False], False)])
+def test_require_page_settings_result(
+ mock_page_key_to_ini, mock_require_setting,
+ required_settings, rs_results, expected):
+ '''Returns the expected result
+ '''
+ mock_require_setting.side_effect = rs_results
+ result = check_settings.require_page_settings(
+ None, required_settings, None, None)
+ assert result == expected
+
+
+def test_require_page_settings_subfunc(
+ mock_page_key_to_ini, mock_require_setting):
+ '''Calls page_key_to_ini() when function is passed to require_setting()
+ '''
+ def mock_rs(x, subkey, z, subkey_to_ini):
+ subkey_to_ini(subkey)
+ return True
+
+ required_settings = ['s1', 's2']
+ mock_require_setting.side_effect = mock_rs
+ check_settings.require_page_settings(None, required_settings, None, None)
+
+ assert mock_page_key_to_ini.call_count == len(required_settings)
+
+
+mock_require_page_settings = testing.make_mock_fixture(
+ check_settings, 'require_page_settings')
+
+
+# validate_url_source()
+
+@pytest.mark.parametrize(
+ ('source', 'expected_error'), [
+ ('/', None),
+ ('/foo', None),
+ ('//www.example.com', None),
+ ('//www.example.com/', None),
+ ('//www.example.com/foo', None),
+ ('http://www.example.com', None),
+ ('https://www.example.com', None),
+ ('anything://www.example.com', None),
+ ('http://www.example.com/', None),
+ ('http://www.example.com/foo', None),
+ # No domain
+ ('//', ex.BadURLSourceError),
+ # Nothing
+ ('', ex.BadURLSourceError),
+ # Missing / after scheme
+ ('http:/www.example.com', ex.BadURLSourceError),
+ # Extra / after scheme
+ ('http:///www.example.com', ex.BadURLSourceError)])
+def test_validate_url_source(mock_page_key_to_ini, source, expected_error):
+ '''The test url produces the expected error, or no error as may be
+ '''
+ errors = []
+ check_settings.validate_url_source(errors, None, source)
+
+ if expected_error:
+ assert len(errors) == 1
+ assert isinstance(errors[0], expected_error)
+ else:
+ assert len(errors) == 0
+
+
+mock_validate_url_source = testing.make_mock_fixture(
+ check_settings, 'validate_url_source')
+
+
+# validate_url_path()
+
+@pytest.mark.parametrize(
+ ('path',), [
+ ('',),
+ ('foo',)])
+def test_validate_url_path_no_slash(mock_page_key_to_ini, path):
+ '''When the path does not begin with a /,
+ the right error is added to errors
+ '''
+ errors = []
+ check_settings.validate_url_path(errors, 'ignored', {'url_path': path})
+
+ assert len(errors) == 1
+ assert isinstance(errors[0], ex.BadFileURLPathError)
+
+
+@pytest.mark.parametrize(
+ ('path',), [
+ ('/',),
+ ('/foo',)])
+def test_validate_url_path_slash(mock_page_key_to_ini, path):
+ '''When the path begins with a '/', no error is added to errors
+ '''
+ errors = []
+ check_settings.validate_url_path(errors, 'ignored', {'url_path': path})
+
+ assert len(errors) == 0
+
+
+mock_validate_url_path = testing.make_mock_fixture(
+ check_settings, 'validate_url_path')
+
+
+# validate_file_source()
+
+@pytest.mark.parametrize(
+ ('source',), [
+ ('',),
+ ('foo',)])
+def test_validate_file_source_no_slash(mock_page_key_to_ini, source):
+ '''When the source does not begin with a /,
+ the right error is added to errors
+ '''
+ errors = []
+ check_settings.validate_file_source(errors, 'ignored', source)
+
+ assert len(errors) == 1
+ assert isinstance(errors[0], ex.BadFileSourceError)
+
+
+@pytest.mark.parametrize(
+ ('source',), [
+ ('/',),
+ ('/foo',)])
+def test_validate_file_source_slash(mock_page_key_to_ini, source):
+ '''When the source begins with a '/', no error is added to errors
+ '''
+ errors = []
+ check_settings.validate_file_source(errors, 'ignored', source)
+
+ assert len(errors) == 0
+
+
+mock_validate_file_source = testing.make_mock_fixture(
+ check_settings, 'validate_file_source')
+
+
+# validate_route_source()
+
+def test_validate_route_source_empty(mock_page_key_to_ini):
+ '''When there is no source the right error is added to errors
+ '''
+ errors = []
+ check_settings.validate_route_source(errors, 'ignored', '')
+
+ assert len(errors) == 1
+ assert isinstance(errors[0], ex.BadRouteSourceError)
+
+
+def test_validate_route_source_not_empty(mock_page_key_to_ini):
+ '''When there is a source no error is added to errors
+ '''
+ errors = []
+ check_settings.validate_route_source(errors, 'ignored', 'something')
+
+ assert len(errors) == 0
+
+
+mock_validate_route_source = testing.make_mock_fixture(
+ check_settings, 'validate_route_source')
+
+
+# validate_asset_source()
+
+def test_validate_asset_source_empty(mock_page_key_to_ini):
+ '''When there is no source the right error is added to errors
+ '''
+ errors = []
+ check_settings.validate_asset_source(errors, 'ignored', '')
+
+ assert len(errors) == 1
+ assert isinstance(errors[0], ex.BadAssetSourceError)
+
+
+def test_validate_asset_source_not_empty(mock_page_key_to_ini):
+ '''When there is a source no error is added to errors
+ '''
+ errors = []
+ check_settings.validate_asset_source(errors, 'ignored', 'something')
+
+ assert len(errors) == 0
+
+
+mock_validate_asset_source = testing.make_mock_fixture(
+ check_settings, 'validate_asset_source')
+
+
+# validate_file_content()
+
+@pytest.mark.parametrize(
+ ('have_settings', 'vup_called'), [
+ (True, 1),
+ (False, 0)])
+def test_validate_file_content(
+ mock_validate_file_source, mock_require_page_settings,
+ mock_validate_url_path,
+ mock_unknown_settings, have_settings, vup_called):
+ '''validate_file_source() is called, validate_url_path()
+ is called when settings validate, the unknown_settings()
+ return value is appended to the errors
+ '''
+ expected_errors = ['some error']
+ mock_require_page_settings.return_value = have_settings
+ mock_unknown_settings.return_value = expected_errors
+
+ errors = []
+ check_settings.validate_file_content(errors, None, None, None)
+
+ mock_validate_file_source.assert_called_once()
+ mock_require_page_settings.assert_called_once()
+ assert mock_validate_url_path.call_count == vup_called
+ assert errors == expected_errors
+
+
+mock_validate_file_content = testing.make_mock_fixture(
+ check_settings, 'validate_file_content')
+
+
+# validate_type_content()
+
+@pytest.mark.parametrize(
+ ('page_settings',
+ 'vus_called',
+ 'vfc_called',
+ 'vrs_called',
+ 'vas_called',
+ 'pkti_called',
+ 'error_class'), [
+ # URL type
+ ({'type': 'URL',
+ 'source': 'ignored'},
+ 1, 0, 0, 0, 0,
+ ex.UnknownSettingKeyError),
+ # file type
+ ({'type': 'file',
+ 'source': 'ignored'},
+ 0, 1, 0, 0, 0,
+ ex.MissingSettingError),
+ # route type
+ ({'type': 'route',
+ 'source': 'ignored'},
+ 0, 0, 1, 0, 0,
+ ex.UnknownSettingKeyError),
+ # asset type
+ ({'type': 'asset',
+ 'source': 'ignored'},
+ 0, 0, 0, 1, 0,
+ ex.UnknownSettingKeyError),
+ # a unknown type
+ ({'type': 'unknown',
+ 'source': 'ignored'},
+ 0, 0, 0, 0, 1,
+ ex.BadPageTypeError)])
+def test_validate_type_content(
+ mock_validate_url_source, mock_unknown_settings,
+ mock_validate_file_content, mock_validate_route_source,
+ mock_validate_asset_source, mock_page_key_to_ini,
+ page_settings, vus_called, vfc_called,
+ vrs_called, vas_called, pkti_called, error_class):
+ '''The expected calls are make, the expected errors returned
+ '''
+ mock_validate_file_content.side_effect = (
+ lambda errors, *args:
+ errors.append(ex.MissingSettingError('ignored')))
+ mock_unknown_settings.return_value = [ex.UnknownSettingKeyError(
+ 'ignored')]
+
+ errors = []
+ check_settings.validate_type_content(errors, 'some_page', page_settings)
+
+ assert mock_validate_url_source.call_count == vus_called
+ assert mock_validate_file_content.call_count == vfc_called
+ assert mock_validate_asset_source.call_count == vas_called
+ assert mock_validate_route_source.call_count == vrs_called
+ assert len(errors) == 1
+ assert isinstance(errors[0], error_class)
+
+
+mock_validate_type_content = testing.make_mock_fixture(
+ check_settings, 'validate_type_content')
+
+
+# validate_page_setting()
+
+def test_validate_page_setting_nopage(
+ mock_require_page_settings, mock_validate_type_content):
+ '''When the page does not have a setting, nothing is done
+ '''
+ errors = []
+ settings = {'pgwui': {}}
+ result = check_settings.validate_page_setting(
+ errors, settings, 'test_page')
+
+ assert errors == []
+ assert result is None
+ mock_require_page_settings.assert_not_called()
+ mock_validate_type_content.assert_not_called()
+
+
+def test_validate_page_setting_not_required(
+ mock_require_page_settings, mock_validate_type_content):
+ '''When require_page_settings() says something is missing, nothing is done
+ '''
+ errors = []
+ settings = {'pgwui': {'test_page': 'ignored'}}
+ mock_require_page_settings.return_value = False
+ result = check_settings.validate_page_setting(
+ errors, settings, 'test_page')
+
+ assert errors == []
+ assert result is None
+ mock_require_page_settings.assert_called_once()
+ mock_validate_type_content.assert_not_called()
+
+
+def test_validate_page_setting_required(
+ mock_require_page_settings, mock_validate_type_content):
+ '''When require_page_settings() says nothing is missing,
+ validate_type_content() is called
+ '''
+ errors = []
+ settings = {'pgwui': {'test_page': 'ignored'}}
+ mock_require_page_settings.return_value = True
+ result = check_settings.validate_page_setting(
+ errors, settings, 'test_page')
+
+ assert errors == []
+ assert result is None
+ mock_require_page_settings.assert_called_once()
+ mock_validate_type_content.assert_called_once()
+
+
+mock_validate_page_setting = testing.make_mock_fixture(
+ check_settings, 'validate_page_setting')
+
+
+# validate_settings()
+
+def test_validate_settings(
+ mock_validate_setting_values, mock_validate_hmac,
+ mock_validate_page_setting):
+ '''The expected calls are made
+ '''
+ check_settings.validate_settings(None, None)
+
+ mock_validate_setting_values.assert_called_once()
+ mock_validate_hmac.assert_called_once()
+ assert mock_validate_page_setting.call_count == 2