diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..e1feabf
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,6 @@
+language: python
+python:
+ - "3.6"
+install: pip install tox-travis
+script:
+ - tox -e pep8,py3
diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py
index 3a5e9a3..c775b9b 100644
--- a/unit_tests/__init__.py
+++ b/unit_tests/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2016 Canonical Ltd
+# Copyright 2019 Canonical Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,7 +16,15 @@ import sys
sys.path.append('src')
sys.path.append('src/lib')
+sys.path.append('src/actions')
# Mock out charmhelpers so that we can test without it.
import charms_openstack.test_mocks # noqa
charms_openstack.test_mocks.mock_charmhelpers()
+
+import mock
+import charms
+keystoneauth1 = mock.MagicMock()
+sys.modules['keystoneauth1'] = keystoneauth1
+charms.leadership = mock.MagicMock()
+sys.modules['charms.leadership'] = charms.leadership
diff --git a/unit_tests/test_actions.py b/unit_tests/test_actions.py
new file mode 100644
index 0000000..e216199
--- /dev/null
+++ b/unit_tests/test_actions.py
@@ -0,0 +1,62 @@
+# Copyright 2019 Canonical Ltd
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+from __future__ import print_function
+
+import mock
+
+import charms_openstack.test_utils as test_utils
+
+import actions
+
+
+class TestKeystoneSAMLMellonActions(test_utils.PatchHelper):
+
+ def setUp(self):
+ super().setUp()
+ self.patch_object(actions.hookenv, 'action_set')
+ self.patch_object(actions.hookenv, 'action_fail')
+ self.patch_object(actions, "os")
+
+ self.patch(
+ "builtins.open", new_callable=mock.mock_open(), name="open")
+ self.file = mock.MagicMock()
+ self.fileobj = mock.MagicMock()
+ self.fileobj.__enter__.return_value = self.file
+ self.open.return_value = self.fileobj
+
+ def test_get_sp_metadata(self):
+ # Valid XML
+ self.sp_metadata_xml = (
+ ""
+ ""
+ " "
+ " ")
+ self.file.readlines.return_value = self.sp_metadata_xml
+ self.metadata_file = ("/etc/apache2/mellon/"
+ "sp-meta.keystone-saml-mellon.xml")
+
+ # File Does not exist
+ self.os.path.exists.return_value = False
+ actions.get_sp_metadata()
+ self.action_fail.assert_called_once_with(
+ "The SP metadata file {} does not exist"
+ .format(self.metadata_file))
+
+ # File exists
+ self.os.path.exists.return_value = True
+ actions.get_sp_metadata()
+ self.action_set.assert_called_once_with(
+ {"output": self.sp_metadata_xml})
diff --git a/unit_tests/test_keystone_saml_mellon_handlers.py b/unit_tests/test_keystone_saml_mellon_handlers.py
new file mode 100644
index 0000000..ef9387a
--- /dev/null
+++ b/unit_tests/test_keystone_saml_mellon_handlers.py
@@ -0,0 +1,151 @@
+# Copyright 2019 Canonical Ltd
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+from __future__ import print_function
+
+import mock
+
+import charm.openstack.keystone_saml_mellon as keystone_saml_mellon
+import reactive.keystone_saml_mellon_handlers as handlers
+
+import charms_openstack.test_utils as test_utils
+
+
+class TestRegisteredHooks(test_utils.TestRegisteredHooks):
+
+ def test_hooks(self):
+ defaults = [
+ 'charm.installed',
+ 'update-status']
+ hook_set = {
+ 'hook': {
+ 'default_upgrade_charm': ('upgrade-charm',),
+ },
+ 'when': {
+ 'render_config': (
+ 'endpoint.keystone-fid-service-provider.joined',
+ 'config.complete',
+ 'keystone-data.complete',),
+ 'config_changed': (
+ 'endpoint.keystone-fid-service-provider.joined',),
+ 'keystone_data_changed': (
+ 'endpoint.keystone-fid-service-provider.joined',),
+ 'configure_websso': (
+ 'endpoint.websso-fid-service-provider.joined',
+ 'config.complete',
+ 'keystone-data.complete',
+ 'config.rendered',),
+ },
+ 'when_not': {
+ 'config_changed': ('config.complete',),
+ 'keystone_departed': (
+ 'endpoint.keystone-fid-service-provider.joined',),
+ 'keystone_data_changed': ('keystone-data.complete',),
+ 'render_config': ('config.rendered',),
+ 'assess_status': ('always.run',),
+ },
+ }
+ # test that the hooks were registered via the
+ # reactive.keystone_saml_mellon_handlers
+ self.registered_hooks_test_helper(handlers, hook_set, defaults)
+
+
+class TestKeystoneSAMLMellonHandlers(test_utils.PatchHelper):
+
+ def setUp(self):
+ super().setUp()
+ self.patch_release(
+ keystone_saml_mellon.KeystoneSAMLMellonCharm.release)
+ self.keystone_saml_mellon_charm = mock.MagicMock()
+ self.patch_object(handlers.charm, 'provide_charm_instance',
+ new=mock.MagicMock())
+ self.provide_charm_instance().__enter__.return_value = (
+ self.keystone_saml_mellon_charm)
+ self.provide_charm_instance().__exit__.return_value = None
+
+ self.patch_object(handlers, 'flags')
+
+ self.uuid = 'uuid-uuid'
+ self.patch_object(handlers.uuid, 'uuid4')
+ self.uuid4.return_value = self.uuid
+
+ self.patch_object(handlers, 'unitdata',
+ new=mock.MagicMock())
+ self.kv = mock.MagicMock()
+ self.unitdata.kv.return_value = self.kv
+
+ self.patch_object(handlers, 'endpoint_from_flag',
+ new=mock.MagicMock())
+ self.endpoint = mock.MagicMock()
+ self.endpoint_from_flag.return_value = self.endpoint
+
+ self.protocol_name = "mapped"
+ self.remote_id_attribute = "https://samltest.id"
+ self.idp_name = "samltest"
+ self.user_facing_name = "samltest.id"
+ self.keystone_saml_mellon_charm.options.protocol_name = (
+ self.protocol_name)
+ self.keystone_saml_mellon_charm.options.remote_id_attribute = (
+ self.remote_id_attribute)
+ self.keystone_saml_mellon_charm.options.idp_name = self.idp_name
+ self.keystone_saml_mellon_charm.options.user_facing_name = (
+ self.user_facing_name)
+
+ self.all_joined_units = []
+ for i in range(0, 2):
+ unit = mock.MagicMock()
+ unit.name = "keystone-{}".format(i)
+ unit.recieved = {"hostname": unit.name,
+ "port": "5000",
+ "tls-enabled": True}
+ self.all_joined_units.append(unit)
+
+ def test_keystone_departed(self):
+ handlers.keystone_departed()
+ self.keystone_saml_mellon_charm.remove_config.assert_called_once_with()
+
+ def test_keystone_data_changed(self):
+ kv_set_calls = [
+ mock.call("tls-enabled", True),
+ mock.call("port", "5000"),
+ mock.call("hostname", "keystone-0"),
+ ]
+
+ handlers.keystone_data_changed(self.endpoint)
+
+ self.kv.set.has_calls(kv_set_calls)
+ self.flags.set_flag.assert_called_once_with('keystone-data.complete')
+
+ def test_render_config(self):
+ handlers.render_config()
+ self.keystone_saml_mellon_charm.render_config.assert_called_once_with()
+ self.flags.set_flag.assert_called_once_with('config.rendered')
+ self.endpoint.publish.assert_called_once_with(
+ self.uuid, self.protocol_name, self.remote_id_attribute)
+
+ def test_config_changed(self):
+ handlers.config_changed()
+ (self.keystone_saml_mellon_charm.configuration_complete
+ .return_value) = True
+ self.flags.set_flag.assert_called_once_with('config.complete')
+
+ def test_configure_websso(self):
+ handlers.configure_websso()
+ self.endpoint.publish.assert_called_once_with(
+ self.protocol_name, self.idp_name, self.user_facing_name)
+
+ def test_assess_status(self):
+ handlers.assess_status()
+ self.keystone_saml_mellon_charm.assess_status.assert_called_once_with()
diff --git a/unit_tests/test_lib_charm_openstack_keystone_saml_mellon.py b/unit_tests/test_lib_charm_openstack_keystone_saml_mellon.py
new file mode 100644
index 0000000..cf67e77
--- /dev/null
+++ b/unit_tests/test_lib_charm_openstack_keystone_saml_mellon.py
@@ -0,0 +1,378 @@
+# Copyright 2019 Canonical Ltd
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+from __future__ import print_function
+
+import mock
+
+import charms_openstack.test_utils as test_utils
+
+import charm.openstack.keystone_saml_mellon as keystone_saml_mellon
+
+
+def FakeConfig(init_dict):
+
+ def _config(key=None):
+ return init_dict[key] if key else init_dict
+
+ return _config
+
+
+def FakeResourceGet(init_dict):
+
+ def _config(key=None):
+ return init_dict[key] if key else init_dict
+
+ return _config
+
+
+class Helper(test_utils.PatchHelper):
+
+ def setUp(self):
+ super().setUp()
+ self.patch_release(
+ keystone_saml_mellon.KeystoneSAMLMellonCharm.release)
+
+ self.patch_object(keystone_saml_mellon, 'unitdata',
+ new=mock.MagicMock())
+ self.kv = mock.MagicMock()
+ self.unitdata.kv.return_value = self.kv
+
+ self.patch_object(keystone_saml_mellon.os_utils, 'os_release',
+ new=mock.MagicMock())
+
+ self.idp_name = "samltest"
+ self.protocol_name = "mapped"
+ self.user_facing_name = "samltest.id"
+ self.nameid_formats = "fake:name:id:format1,fake:name:id:format2"
+ self.test_config = {
+ "idp-name": self.idp_name,
+ "protocol-name": self.protocol_name,
+ "user-facing-name": self.user_facing_name,
+ "nameid-formats": self.nameid_formats,
+ "subject-confirmation-data-address-check": False
+ }
+ self.resources = {
+ "idp-metadata": "/path/to/idp-metadata.xml",
+ "sp-private-key": "/path/to/sp-private-key.pem",
+ "sp-signing-keyinfo": "/path/to/sp-signing-keyinfo.xml"
+ }
+ self.patch_object(keystone_saml_mellon.hookenv, 'config',
+ side_effect=FakeConfig(self.test_config))
+ self.patch_object(keystone_saml_mellon.hookenv, 'resource_get',
+ side_effect=FakeResourceGet(self.resources))
+ self.patch_object(
+ keystone_saml_mellon.hookenv, 'application_version_set')
+ self.patch_object(keystone_saml_mellon.hookenv, 'status_set')
+ self.patch_object(keystone_saml_mellon.ch_host, 'mkdir')
+ self.patch_object(keystone_saml_mellon.core.templating, 'render')
+
+ self.template_loader = mock.MagicMock()
+ self.patch_object(keystone_saml_mellon.os_templating, 'get_loader',
+ return_value=self.template_loader)
+ self.patch_object(
+ keystone_saml_mellon.KeystoneSAMLMellonCharm,
+ 'application_version',
+ return_value="1.0.0")
+
+ self.patch_object(
+ keystone_saml_mellon.KeystoneSAMLMellonCharm, 'render_configs')
+ self.patch_object(keystone_saml_mellon, 'os')
+ self.patch_object(keystone_saml_mellon, 'subprocess')
+
+ self.patch(
+ "builtins.open", new_callable=mock.mock_open(), name="open")
+ self.file = mock.MagicMock()
+ self.fileobj = mock.MagicMock()
+ self.fileobj.__enter__.return_value = self.file
+ self.open.return_value = self.fileobj
+
+
+class TestKeystoneSAMLMellonUtils(Helper):
+
+ def test_select_release(self):
+ self.kv.get.return_value = 'mitaka'
+ self.assertEqual(
+ keystone_saml_mellon.select_release(), 'mitaka')
+
+ self.kv.get.return_value = None
+ self.os_release.return_value = 'rocky'
+ self.assertEqual(
+ keystone_saml_mellon.select_release(), 'rocky')
+
+
+class TestKeystoneSAMLMellonConfigurationAdapter(Helper):
+
+ def setUp(self):
+ super().setUp()
+ self.hostname = "keystone-sp.local"
+ self.port = "5000"
+ self.tls_enabled = True
+ self.unitdata_data = {
+ "hostname": self.hostname,
+ "port": self.port,
+ "tls-enabled": self.tls_enabled,
+ }
+ self.kv.get.side_effect = FakeConfig(self.unitdata_data)
+ self.base_url = "https://{}:{}".format(self.hostname, self.port)
+
+ def test_validation_errors(self):
+ errors = {"idp-metadata": "Bad XML"}
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ ksmca._validation_errors = errors
+ self.assertEqual(ksmca.validation_errors, errors)
+
+ def test_remote_id_attribute(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(ksmca.remote_id_attribute, "MELLON_IDP")
+
+ def test_idp_metadata_file(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(
+ ksmca.idp_metadata_file, keystone_saml_mellon.IDP_METADATA)
+
+ def test_sp_metadata_file(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(
+ ksmca.sp_metadata_file, keystone_saml_mellon.SP_METADATA)
+
+ def test_sp_private_key_file(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(
+ ksmca.sp_private_key_file, keystone_saml_mellon.SP_PRIVATE_KEY)
+
+ def test_keystone_host(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(ksmca.keystone_host, self.hostname)
+
+ def test_keystone_port(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(ksmca.keystone_port, self.port)
+
+ def test_keystone_tls_enabled(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(ksmca.tls_enabled, self.tls_enabled)
+
+ def test_keystone_base_url(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(ksmca.keystone_base_url, self.base_url)
+
+ def test_sp_idp_path(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(
+ ksmca.sp_idp_path,
+ '/v3/OS-FEDERATION/identity_providers/{}'.format(self.idp_name))
+
+ def test_sp_protocol_path(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(
+ ksmca.sp_protocol_path,
+ '{}/protocols/{}'.format(ksmca.sp_idp_path, self.protocol_name))
+
+ def test_sp_auth_path(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(
+ ksmca.sp_auth_path, '{}/auth'.format(ksmca.sp_protocol_path))
+
+ def test_mellon_endpoint_path(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(
+ ksmca.mellon_endpoint_path, '{}/mellon'.format(ksmca.sp_auth_path))
+
+ def test_websso_auth_idp_protocol_path(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(
+ ksmca.websso_auth_idp_protocol_path,
+ ('/v3/auth/OS-FEDERATION/identity_providers/{}/protocols/{}/websso'
+ .format(self.idp_name, self.protocol_name)))
+
+ def test_sp_post_response_path(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(
+ ksmca.sp_post_response_path,
+ '{}/postResponse'.format(ksmca.mellon_endpoint_path))
+
+ def test_sp_logout_path(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(
+ ksmca.sp_logout_path,
+ '{}/logout'.format(ksmca.mellon_endpoint_path))
+
+ def test_sp_auth_url(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(
+ ksmca.sp_auth_url,
+ '{}{}'.format(ksmca.keystone_base_url, ksmca.sp_auth_path))
+
+ def test_sp_logout_url(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(
+ ksmca.sp_logout_url,
+ '{}{}'.format(ksmca.keystone_base_url, ksmca.sp_logout_path))
+
+ def test_sp_post_response_url(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(
+ ksmca.sp_post_response_url,
+ '{}{}'.format(ksmca.keystone_base_url,
+ ksmca.sp_post_response_path))
+
+ def test_mellon_subject_confirmation_data_address_check(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(
+ ksmca.mellon_subject_confirmation_data_address_check,
+ 'Off')
+
+ def test_supported_nameid_formats(self):
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ self.assertEqual(
+ ksmca.supported_nameid_formats, self.nameid_formats.split(","))
+
+ def test_idp_metadata(self):
+ self.os.path.exists.return_value = True
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ # Valid XML
+ self.idp_metadata_xml = (
+ ""
+ " "
+ "")
+ self.file.read.return_value = self.idp_metadata_xml
+ self.assertEqual(ksmca.idp_metadata, self.idp_metadata_xml)
+ self.open.assert_called_with(self.resources["idp-metadata"])
+
+ # Inalid XML
+ ksmca._idp_metadata = None
+ self.file.read.return_value = "INVALID XML"
+ self.assertEqual(ksmca.idp_metadata, "")
+ self.assertEqual(
+ ksmca._validation_errors,
+ {"idp-metadata": ksmca.IDP_METADATA_INVALID})
+
+ def test_sp_signing_keyinfo(self):
+ self.os.path.exists.return_value = True
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ # Valid XML
+ self.sp_signing_keyinfo_xml = (
+ ""
+ ""
+ " "
+ " ")
+ self.file.read.return_value = self.sp_signing_keyinfo_xml
+ self.assertEqual(ksmca.sp_signing_keyinfo, self.sp_signing_keyinfo_xml)
+ self.open.assert_called_with(self.resources["sp-signing-keyinfo"])
+
+ # Inalid XML
+ ksmca._sp_signing_keyinfo = None
+ self.file.read.return_value = "INVALID XML"
+ self.assertEqual(ksmca.sp_signing_keyinfo, "")
+ self.assertEqual(
+ ksmca._validation_errors,
+ {"sp-signing-keyinfo": ksmca.SP_SIGNING_KEYINFO_INVALID})
+
+ def test_sp_private_key(self):
+ self.os.path.exists.return_value = True
+ ksmca = keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter()
+ # Valid Key
+ self.sp_private_key_pem = ("""
+-----BEGIN RSA PRIVATE KEY-----
+MIIBPAIBAAJBANLUtlT9JMQ/RcGEipW6MBtUoFBGMOclUmOpP1BbaFJoBn19J0UG
+STj29M9nDLDRdfP0O/JiisG6ejxmO0A0xTsCAwEAAQJBAKT0IKRmW3ngN2etl/CF
++FWp5LRp9qEjJk8rgIoSupCdvuT0Q6XLk/ygHeiBYcKTf2pT/PWjQxg1pD7So5K8
+YcECIQD5SKfItJ5YC9mD+6H28UqQATPehRPhQEEFIl/lJCrFgwIhANiC14XvcuWc
+xMy1Lcc5lFkrB+b+oWVKJyMpNTHgXivpAiEAqh0FurZfNDBp8GJgpbcFrf3UGq7v
+4RBLDqjljeY/decCIEk3/lDCCFYULQ2ZW9Da7Qs2nSaGB+isKg4e+mlSmiY5AiEA
+lAoUNjDHWBOlyXziqZiufMURqbPPbRkEjWwN8G2r15A=
+-----END RSA PRIVATE KEY-----
+ """)
+ self.file.read.return_value = self.sp_private_key_pem
+ self.assertEqual(ksmca.sp_private_key, self.sp_private_key_pem)
+ self.open.assert_called_with(self.resources["sp-private-key"])
+
+ # Invalid Key
+ ksmca._sp_private_key = None
+ self.file.read.return_value = "INVALID PEM KEY"
+ self.assertEqual(ksmca.sp_private_key, '')
+ self.assertEqual(
+ ksmca._validation_errors,
+ {"sp-private-key": ksmca.SP_PRIVATE_KEY_INVALID})
+
+
+class TestKeystoneSAMLMellonCharm(Helper):
+
+ def setUp(self):
+ super().setUp()
+ self.patch_object(
+ keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter,
+ 'idp_metadata')
+ self.patch_object(
+ keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter,
+ 'sp_private_key')
+ self.patch_object(
+ keystone_saml_mellon.KeystoneSAMLMellonConfigurationAdapter,
+ 'sp_signing_keyinfo')
+ self.idp_metadata.return_value = self.resources["idp-metadata"]
+ self.idp_metadata.__bool__.return_value = True
+ self.sp_private_key.return_value = self.resources["sp-private-key"]
+ self.sp_private_key.__bool__.return_value = True
+ self.sp_signing_keyinfo.return_value = self.resources[
+ "sp-signing-keyinfo"]
+ self.sp_signing_keyinfo.__bool__.return_value = True
+
+ def test_configuration_complete(self):
+ ksm = keystone_saml_mellon.KeystoneSAMLMellonCharm()
+ self.assertTrue(ksm.configuration_complete())
+
+ # One option not ready
+ self.sp_signing_keyinfo.__bool__.return_value = False
+ self.assertFalse(ksm.configuration_complete())
+
+ def test_assess_status(self):
+ ksm = keystone_saml_mellon.KeystoneSAMLMellonCharm()
+ ksm.assess_status()
+ self.application_version_set.asert_called_once_with()
+ self.status_set.assert_called_once_with("active", "Unit is ready")
+
+ # One option not ready
+ self.status_set.reset_mock()
+ self.sp_signing_keyinfo.__bool__.return_value = False
+ ksm.options._validation_errors = {"idp-metadata": "malformed"}
+ ksm.assess_status()
+ self.status_set.assert_called_once_with(
+ "blocked", "Configuration is incomplete. idp-metadata: malformed")
+
+ def test_render_config(self):
+ ksm = keystone_saml_mellon.KeystoneSAMLMellonCharm()
+ ksm.render_config()
+ self.assertEqual(self.render_configs.call_count, 1)
+ self.assertEqual(self.render.call_count, 2)
+
+ def test_remove_config(self):
+ self.os.path.exists.return_value = True
+ ksm = keystone_saml_mellon.KeystoneSAMLMellonCharm()
+ ksm.remove_config()
+ self.assertEqual(self.os.path.exists.call_count, 4)
+ self.assertEqual(self.os.unlink.call_count, 4)
+
+ def test_enable_module(self):
+ ksm = keystone_saml_mellon.KeystoneSAMLMellonCharm()
+ ksm.enable_module()
+ self.subprocess.check_call.assert_called_once_with(
+ ['a2enmod', 'auth_mellon'])
+
+ def test_disable_module(self):
+ ksm = keystone_saml_mellon.KeystoneSAMLMellonCharm()
+ ksm.disable_module()
+ self.subprocess.check_call.assert_called_once_with(
+ ['a2dismod', 'auth_mellon'])