diff --git a/docs/add-mock.md b/docs/add-mock.md index 478eb20..468b77e 100644 --- a/docs/add-mock.md +++ b/docs/add-mock.md @@ -2,5 +2,5 @@ Contribute a New Mock ====== 1. Create a new [Runnable](../touchstone/lib/mocks/runnables) or [Networked Runnable](../touchstone/lib/mocks/networked_runnables) 1. Add a new property to the [Mocks](../touchstone/lib/mocks/mocks.py) class, so your new mock is accessible in user test cases - 1. Build a concrete instance of your new mock in the [Bootstrap](../touchstone/bootstrap.py) `__build_mocks` method with its required dependencies + 1. Build a concrete instance of your new mock in the [Mock Factory](../touchstone/lib/mocks/mock_factory.py) with its required dependencies 1. Write [unit](../tests) and [Touchstone tests](../touchstone-tests) for your new mock diff --git a/tests/test_common.py b/tests/test_common.py index 0a372df..33dfd0e 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -1,34 +1,10 @@ import unittest -from unittest import TestCase, mock +from unittest import TestCase from touchstone import common class TestCommon(TestCase): - @mock.patch('touchstone.common.os') - def test_sanityCheckPasses_requirementsMet_ReturnsTrue(self, mock_os): - # Given - mock_os.getcwd.return_value = 'temp' - mock_os.path.exists.return_value = True - - # When - result = common.sanity_check_passes() - - # Then - self.assertTrue(result) - - @mock.patch('touchstone.common.os') - def test_sanityCheckPasses_requirementsNotMet_ReturnsFalse(self, mock_os): - # Given - mock_os.getcwd.return_value = 'temp' - mock_os.path.exists.return_value = False - - # When - result = common.sanity_check_passes() - - # Then - self.assertFalse(result) - def test_dictMerge_emptyOverride_ReturnsBase(self): # Given base = {'foo': 'bar'} diff --git a/touchstone/bootstrap.py b/touchstone/bootstrap.py index 1c8f79f..ae6d1a2 100644 --- a/touchstone/bootstrap.py +++ b/touchstone/bootstrap.py @@ -37,15 +37,14 @@ def __build_touchstone_config(self, root) -> TouchstoneConfig: return config def __build_mocks(self, root, configs, host) -> Mocks: - defaults = {} + defaults_paths = {} default_files = glob.glob(os.path.join(root, 'defaults') + '/*.yml') for default_file in default_files: - with open(default_file, 'r') as file: - defaults[Path(default_file).stem] = yaml.safe_load(file) + defaults_paths[Path(default_file).stem] = default_file mocks = Mocks() for mock_name in configs: - mock_factory = MockFactory(self.is_dev_mode, root, defaults, configs, host, self.docker_manager) + mock_factory = MockFactory(self.is_dev_mode, root, defaults_paths, configs, host, self.docker_manager) mock = mock_factory.get_mock(mock_name) if not mock: raise exceptions.MockNotSupportedException(f'Mock: {mock_name} is not supported.') diff --git a/touchstone/lib/mocks/configurers/FileConfigurer.py b/touchstone/lib/mocks/configurers/FileConfigurer.py new file mode 100644 index 0000000..5dbf903 --- /dev/null +++ b/touchstone/lib/mocks/configurers/FileConfigurer.py @@ -0,0 +1,20 @@ +import yaml + +from touchstone import common +from touchstone.lib.mocks.configurers.i_configurable import IConfigurable + + +class FileConfigurer(IConfigurable): + def __init__(self, config_path: str = None): + self.__config_path = config_path + self.__override_config = {} + + def get_config(self) -> dict: + if not self.__config_path: + return {} + with open(self.__config_path, 'r') as file: + config = yaml.safe_load(file) + return common.dict_merge(config, self.__override_config) + + def merge_config(self, other: dict): + self.__override_config = other diff --git a/touchstone/lib/mocks/mock_factory.py b/touchstone/lib/mocks/mock_factory.py index e211efa..36bfd85 100644 --- a/touchstone/lib/mocks/mock_factory.py +++ b/touchstone/lib/mocks/mock_factory.py @@ -3,6 +3,7 @@ from touchstone.lib.docker_manager import DockerManager from touchstone.lib.mocks.configurers.BasicConfigurer import BasicConfigurer +from touchstone.lib.mocks.configurers.FileConfigurer import FileConfigurer from touchstone.lib.mocks.health_checks.http_health_check import HttpHealthCheck from touchstone.lib.mocks.mockables.basic_mock import BasicMock from touchstone.lib.mocks.mockables.i_mockable import IMockable @@ -33,59 +34,66 @@ class MockFactory(object): - def __init__(self, is_dev_mode: bool, root: str, defaults: dict, configs: dict, host: str, + def __init__(self, is_dev_mode: bool, root: str, defaults_paths: dict, configs: dict, host: str, docker_manager: DockerManager): self.__is_dev_mode = is_dev_mode self.__root = root - self.__defaults = defaults + self.__defaults_paths = defaults_paths self.__configs = configs self.__host = host self.__docker_manager = docker_manager def get_mock(self, mock_name: str) -> Optional[IMockable]: config = self.__configs.get(mock_name, {}) - mock_defaults = self.__defaults.get(mock_name, {}) + mock_defaults_paths = self.__defaults_paths.get(mock_name, None) mock = None if mock_name == 'http': - runnable = DockerHttpRunnable(mock_defaults, HttpHealthCheck(), DockerHttpSetup(), DockerHttpVerify(), + defaults_configurer = FileConfigurer(mock_defaults_paths) + runnable = DockerHttpRunnable(defaults_configurer, HttpHealthCheck(), DockerHttpSetup(), DockerHttpVerify(), self.__docker_manager) mock = NetworkedMock('http', 'HTTP', self.__host, runnable) elif mock_name == 'rabbitmq': + defaults_configurer = FileConfigurer(mock_defaults_paths) configurer = BasicConfigurer(IRabbitmqBehavior.DEFAULT_CONFIG) configurer.merge_config(config) context = DockerRabbitmqContext() setup = DockerRabbitmqSetup(context) verify = DockerRabbitmqVerify(context) - runnable = DockerRabbitmqRunnable(mock_defaults, configurer, HttpHealthCheck(), setup, verify, + runnable = DockerRabbitmqRunnable(defaults_configurer, configurer, HttpHealthCheck(), setup, verify, self.__docker_manager) mock = NetworkedMock('rabbitmq', 'Rabbit MQ', self.__host, runnable) elif mock_name == 'mongodb': + defaults_configurer = FileConfigurer(mock_defaults_paths) context = DockerMongoContext() setup = DockerMongodbSetup(context) verify = DockerMongodbVerify(context) - runnable = DockerMongodbRunnable(mock_defaults, self.__is_dev_mode, setup, verify, self.__docker_manager) + runnable = DockerMongodbRunnable(defaults_configurer, self.__is_dev_mode, setup, verify, + self.__docker_manager) mock = NetworkedMock('mongodb', 'Mongo DB', self.__host, runnable) elif mock_name == 'mysql': + defaults_configurer = FileConfigurer(mock_defaults_paths) configurer = BasicConfigurer(IMysqlBehavior.DEFAULT_CONFIG) configurer.merge_config(config) context = DockerMysqlContext() setup = DockerMysqlSetup(context) verify = DockerMysqlVerify(context) - runnable = DockerMysqlRunnable(self.__is_dev_mode, mock_defaults, configurer, setup, verify, + runnable = DockerMysqlRunnable(defaults_configurer, self.__is_dev_mode, configurer, setup, verify, self.__docker_manager) mock = NetworkedMock('mysql', 'MySQL', self.__host, runnable) elif mock_name == 's3': + defaults_configurer = FileConfigurer(mock_defaults_paths) base_objects_path = os.path.join(self.__root, 'defaults') setup = DockerS3Setup() verify = DockerS3Verify() - runnable = DockerS3Runnable(mock_defaults, base_objects_path, HttpHealthCheck(), setup, verify, + runnable = DockerS3Runnable(defaults_configurer, base_objects_path, HttpHealthCheck(), setup, verify, self.__docker_manager) mock = NetworkedMock('s3', 'S3', self.__host, runnable) elif mock_name == 'filesystem': + defaults_configurer = FileConfigurer(mock_defaults_paths) base_files_path = os.path.join(self.__root, 'defaults') setup = LocalFilesystemSetup(base_files_path) verify = LocalFilesystemVerify(base_files_path) - runnable = LocalFilesystemRunnable(mock_defaults, base_files_path, setup, verify) + runnable = LocalFilesystemRunnable(defaults_configurer, base_files_path, setup, verify) mock = BasicMock('filesystem', 'Filesystem', runnable) return mock diff --git a/touchstone/lib/mocks/networked_runnables/http/docker/docker_http_runnable.py b/touchstone/lib/mocks/networked_runnables/http/docker/docker_http_runnable.py index 59914a0..58edd50 100644 --- a/touchstone/lib/mocks/networked_runnables/http/docker/docker_http_runnable.py +++ b/touchstone/lib/mocks/networked_runnables/http/docker/docker_http_runnable.py @@ -1,5 +1,6 @@ from touchstone.lib import exceptions from touchstone.lib.docker_manager import DockerManager +from touchstone.lib.mocks.configurers.i_configurable import IConfigurable from touchstone.lib.mocks.health_checks.i_url_health_checkable import IUrlHealthCheckable from touchstone.lib.mocks.network import Network from touchstone.lib.mocks.networked_runnables.http.docker.docker_http_setup import DockerHttpSetup @@ -9,9 +10,9 @@ class DockerHttpRunnable(INetworkedRunnable, IHttpBehavior): - def __init__(self, defaults: dict, health_check: IUrlHealthCheckable, setup: DockerHttpSetup, + def __init__(self, defaults_configurer: IConfigurable, health_check: IUrlHealthCheckable, setup: DockerHttpSetup, verify: DockerHttpVerify, docker_manager: DockerManager): - self.__defaults = defaults + self.__defaults_configurer = defaults_configurer self.__health_check = health_check self.__setup = setup self.__verify = verify @@ -27,7 +28,7 @@ def get_network(self) -> Network: def initialize(self): self.__setup.set_url(self.get_network().external_url()) self.__verify.set_url(self.get_network().external_url()) - self.__setup.init(self.__defaults) + self.__setup.init(self.__defaults_configurer.get_config()) def start(self): run_result = self.__docker_manager.run_image('holomekc/wiremock-gui:2.25.1', port=8080, exposed_port=9090) @@ -44,7 +45,7 @@ def stop(self): self.__docker_manager.stop_container(self.__container_id) def reset(self): - self.__setup.init(self.__defaults) + self.__setup.init(self.__defaults_configurer.get_config()) def services_available(self): pass diff --git a/touchstone/lib/mocks/networked_runnables/mongodb/docker/docker_mongodb_runnable.py b/touchstone/lib/mocks/networked_runnables/mongodb/docker/docker_mongodb_runnable.py index 0d3093f..413f03b 100644 --- a/touchstone/lib/mocks/networked_runnables/mongodb/docker/docker_mongodb_runnable.py +++ b/touchstone/lib/mocks/networked_runnables/mongodb/docker/docker_mongodb_runnable.py @@ -2,6 +2,7 @@ from touchstone.lib import exceptions from touchstone.lib.docker_manager import DockerManager +from touchstone.lib.mocks.configurers.i_configurable import IConfigurable from touchstone.lib.mocks.network import Network from touchstone.lib.mocks.networked_runnables.i_networked_runnable import INetworkedRunnable from touchstone.lib.mocks.networked_runnables.mongodb.docker.docker_mongodb_setup import DockerMongodbSetup @@ -11,9 +12,9 @@ class DockerMongodbRunnable(INetworkedRunnable, IMongodbBehavior): - def __init__(self, defaults: dict, is_dev_mode: bool, setup: DockerMongodbSetup, verify: DockerMongodbVerify, - docker_manager: DockerManager): - self.__defaults = defaults + def __init__(self, defaults_configurer: IConfigurable, is_dev_mode: bool, setup: DockerMongodbSetup, + verify: DockerMongodbVerify, docker_manager: DockerManager): + self.__defaults_configurer = defaults_configurer self.__is_dev_mode = is_dev_mode self.__setup = setup self.__verify = verify @@ -31,7 +32,7 @@ def initialize(self): mongo_client = pymongo.MongoClient(self.get_network().external_host, self.get_network().external_port) self.__setup.set_mongo_client(mongo_client) self.__verify.set_mongo_client(mongo_client) - self.__setup.init(self.__defaults) + self.__setup.init(self.__defaults_configurer.get_config()) def start(self): run_result = self.__docker_manager.run_image('mongo:4.0.14', port=27017) @@ -59,7 +60,7 @@ def stop(self): self.__docker_manager.stop_container(self.__ui_container_id) def reset(self): - self.__setup.init(self.__defaults) + self.__setup.init(self.__defaults_configurer.get_config()) def services_available(self): pass diff --git a/touchstone/lib/mocks/networked_runnables/mysql/docker/docker_mysql_runnable.py b/touchstone/lib/mocks/networked_runnables/mysql/docker/docker_mysql_runnable.py index 6e767d9..c12d1ff 100644 --- a/touchstone/lib/mocks/networked_runnables/mysql/docker/docker_mysql_runnable.py +++ b/touchstone/lib/mocks/networked_runnables/mysql/docker/docker_mysql_runnable.py @@ -14,10 +14,10 @@ class DockerMysqlRunnable(INetworkedRunnable, IMysqlBehavior): __USERNAME = 'root' __PASSWORD = 'root' - def __init__(self, is_dev_mode: bool, defaults: dict, configurer: IConfigurable, setup: DockerMysqlSetup, - verify: DockerMysqlVerify, docker_manager: DockerManager): + def __init__(self, defaults_configurer: IConfigurable, is_dev_mode: bool, configurer: IConfigurable, + setup: DockerMysqlSetup, verify: DockerMysqlVerify, docker_manager: DockerManager): + self.__defaults_configurer = defaults_configurer self.__is_dev_mode = is_dev_mode - self.__defaults = defaults self.__configurer = configurer self.__setup = setup self.__verify = verify @@ -45,7 +45,7 @@ def initialize(self): self.__setup.set_convert_camel_to_snake(convert_camel_to_snake) self.__verify.set_cursor(cursor) self.__verify.set_convert_camel_to_snake(convert_camel_to_snake) - self.__setup.init(self.__defaults) + self.__setup.init(self.__defaults_configurer.get_config()) def start(self): run_result = self.__docker_manager.run_image('mysql:5.7.29', port=3306, @@ -75,7 +75,7 @@ def stop(self): self.__docker_manager.stop_container(self.__ui_container_id) def reset(self): - self.__setup.init(self.__defaults) + self.__setup.init(self.__defaults_configurer.get_config()) def services_available(self): pass diff --git a/touchstone/lib/mocks/networked_runnables/rabbitmq/docker/docker_rabbitmq_runnable.py b/touchstone/lib/mocks/networked_runnables/rabbitmq/docker/docker_rabbitmq_runnable.py index 9cdf4e0..23e4473 100644 --- a/touchstone/lib/mocks/networked_runnables/rabbitmq/docker/docker_rabbitmq_runnable.py +++ b/touchstone/lib/mocks/networked_runnables/rabbitmq/docker/docker_rabbitmq_runnable.py @@ -16,9 +16,9 @@ class DockerRabbitmqRunnable(INetworkedRunnable, IRabbitmqBehavior): __USERNAME = 'guest' __PASSWORD = 'guest' - def __init__(self, defaults: dict, configurer: IConfigurable, health_check: IUrlHealthCheckable, + def __init__(self, defaults_configurer: IConfigurable, configurer: IConfigurable, health_check: IUrlHealthCheckable, setup: DockerRabbitmqSetup, verify: DockerRabbitmqVerify, docker_manager: DockerManager): - self.__defaults = defaults + self.__defaults_configurer = defaults_configurer self.__configurer = configurer self.__health_check = health_check self.__setup = setup @@ -45,7 +45,7 @@ def initialize(self): self.__setup.set_connection_params(connection_params) self.__verify.set_blocking_channel(channel) if self.__configurer.get_config()['autoCreate']: - self.__setup.create_all(self.__defaults) + self.__setup.create_all(self.__defaults_configurer.get_config()) def start(self): run_result = self.__docker_manager.run_image('rabbitmq:3.7.22-management-alpine', port=5672, ui_port=15672) @@ -67,7 +67,7 @@ def reset(self): def services_available(self): if not self.__configurer.get_config()['autoCreate']: - self.__setup.create_shadow_queues(self.__defaults) + self.__setup.create_shadow_queues(self.__defaults_configurer.get_config()) def is_healthy(self) -> bool: self.__health_check.set_url(self.get_network().ui_url()) diff --git a/touchstone/lib/mocks/networked_runnables/s3/docker/docker_s3_runnable.py b/touchstone/lib/mocks/networked_runnables/s3/docker/docker_s3_runnable.py index 3f53d31..7abd3b2 100644 --- a/touchstone/lib/mocks/networked_runnables/s3/docker/docker_s3_runnable.py +++ b/touchstone/lib/mocks/networked_runnables/s3/docker/docker_s3_runnable.py @@ -2,6 +2,7 @@ from touchstone.lib import exceptions from touchstone.lib.docker_manager import DockerManager +from touchstone.lib.mocks.configurers.i_configurable import IConfigurable from touchstone.lib.mocks.health_checks.i_url_health_checkable import IUrlHealthCheckable from touchstone.lib.mocks.network import Network from touchstone.lib.mocks.networked_runnables.i_networked_runnable import INetworkedRunnable @@ -14,9 +15,9 @@ class DockerS3Runnable(INetworkedRunnable, IS3Behavior): __USERNAME = 'admin123' __PASSWORD = 'admin123' - def __init__(self, defaults: dict, base_objects_path: str, health_check: IUrlHealthCheckable, setup: DockerS3Setup, - verify: DockerS3Verify, docker_manager: DockerManager): - self.__defaults = defaults + def __init__(self, defaults_configurer: IConfigurable, base_objects_path: str, health_check: IUrlHealthCheckable, + setup: DockerS3Setup, verify: DockerS3Verify, docker_manager: DockerManager): + self.__defaults_configurer = defaults_configurer self.__base_objects_path = base_objects_path self.__health_check = health_check self.__setup = setup @@ -37,7 +38,7 @@ def initialize(self): secure=False) self.__setup.set_s3_client(s3_client) self.__verify.set_s3_client(s3_client) - self.__setup.init(self.__base_objects_path, self.__defaults) + self.__setup.init(self.__base_objects_path, self.__defaults_configurer.get_config()) def start(self): run_result = self.__docker_manager.run_image('minio/minio:RELEASE.2020-02-27T00-23-05Z server /data', @@ -57,7 +58,7 @@ def stop(self): self.__docker_manager.stop_container(self.__container_id) def reset(self): - self.__setup.init(self.__base_objects_path, self.__defaults) + self.__setup.init(self.__base_objects_path, self.__defaults_configurer.get_config()) def services_available(self): pass diff --git a/touchstone/lib/mocks/runnables/filesystem/local/local_filesystem_runnable.py b/touchstone/lib/mocks/runnables/filesystem/local/local_filesystem_runnable.py index 9831887..cb06623 100644 --- a/touchstone/lib/mocks/runnables/filesystem/local/local_filesystem_runnable.py +++ b/touchstone/lib/mocks/runnables/filesystem/local/local_filesystem_runnable.py @@ -1,4 +1,5 @@ from touchstone.lib import exceptions +from touchstone.lib.mocks.configurers.i_configurable import IConfigurable from touchstone.lib.mocks.runnables.filesystem.i_filesystem_behavior import IFilesystemBehavior, IFilesystemVerify, \ IFilesystemSetup from touchstone.lib.mocks.runnables.filesystem.local.local_filesystem_setup import LocalFilesystemSetup @@ -7,21 +8,21 @@ class LocalFilesystemRunnable(IRunnable, IFilesystemBehavior): - def __init__(self, defaults: dict, base_files_path: str, setup: LocalFilesystemSetup, + def __init__(self, defaults_configurer: IConfigurable, base_files_path: str, setup: LocalFilesystemSetup, verify: LocalFilesystemVerify): - self.__defaults = defaults + self.__defaults_configurer = defaults_configurer self.__base_files_path = base_files_path self.__setup = setup self.__verify = verify def start(self): - self.__setup.init(self.__defaults) + self.__setup.init(self.__defaults_configurer.get_config()) def stop(self): - self.__setup.delete_defaults(self.__defaults) + self.__setup.delete_defaults(self.__defaults_configurer.get_config()) def reset(self): - self.__setup.init(self.__defaults) + self.__setup.init(self.__defaults_configurer.get_config()) def services_available(self): pass