From 4cf23bc61334e23868cbf4c33d50705c5dbd0933 Mon Sep 17 00:00:00 2001 From: Bracey Summers <35816572+bsummers-tc@users.noreply.github.com> Date: Thu, 19 Oct 2023 15:00:21 -0500 Subject: [PATCH] Secure Redis support update (SSL and User/Pass) APP-4245 - [Inputs] Added support for secure Redis (SSL and User/Pass) APP-4246 - [Playbook] Removed support for AOT execution --- pyproject.toml | 2 + release_notes.md | 5 + tcex/__metadata__.py | 2 +- tcex/app/app.py | 6 ++ tcex/app/config | 2 +- tcex/app/key_value_store | 2 +- tcex/app/playbook | 2 +- tcex/exit/exit.py | 38 ------- tcex/input/input.py | 99 +------------------ .../model/aot_execution_enabled_model.py | 34 ------- tcex/input/model/cert_model.py | 43 ++++++++ tcex/input/model/common_model.py | 3 +- tcex/input/model/model_map.py | 2 - tcex/input/model/module_app_model.py | 3 +- tcex/input/model/playbook_common_model.py | 10 ++ tcex/input/model/service_model.py | 20 ---- tests/api/tc/v3/v3_helpers.py | 13 +++ tests/app/config/apps/tcpb/app_1/app.yaml | 1 - .../install-template.json | 1 - tests/app/playbook/test_playbook_aot.py | 97 ------------------ tests/input/test_input.py | 50 ---------- tests/mock_app.py | 3 - 22 files changed, 89 insertions(+), 349 deletions(-) delete mode 100644 tcex/input/model/aot_execution_enabled_model.py create mode 100644 tcex/input/model/cert_model.py delete mode 100644 tests/app/playbook/test_playbook_aot.py diff --git a/pyproject.toml b/pyproject.toml index 06284c503..54084c6e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,6 +109,8 @@ ignore = [ [tool.pylint.messages_control] disable = [ "broad-exception-caught", + "cyclic-import", # pylint incorrectly identifies cyclic imports + "duplicate-code", "fixme", "import-outside-toplevel", "invalid-name", diff --git a/release_notes.md b/release_notes.md index dfa44169f..cff72d256 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,5 +1,10 @@ # Release Notes +## 4.0.3 + +- APP-4245 - [Inputs] Added support for secure Redis (SSL and User/Pass) +- APP-4246 - [Playbook] Removed support for AOT execution + ## 4.0.2 - APP-4155 - [API] Added Mitre Attack module for lookup by id or name diff --git a/tcex/__metadata__.py b/tcex/__metadata__.py index 1a4990914..9a9d1ac20 100644 --- a/tcex/__metadata__.py +++ b/tcex/__metadata__.py @@ -1,3 +1,3 @@ """TcEx Framework Module""" __license__ = 'Apache-2.0' -__version__ = '4.0.2' +__version__ = '4.0.3' diff --git a/tcex/app/app.py b/tcex/app/app.py index 5c3016f79..0c412f1f1 100644 --- a/tcex/app/app.py +++ b/tcex/app/app.py @@ -62,8 +62,14 @@ def key_value_store(self) -> KeyValueStore: self.model.tc_kvstore_host, self.model.tc_kvstore_port, self.model.tc_kvstore_type, + self.model.tc_playbook_kvstore_id, self.model.tc_kvstore_pass, self.model.tc_kvstore_user, + self.model.tc_kvstore_tls_enabled, + self.model.tc_kvstore_tls_port, + self.model.tc_svc_broker_cacert_file, + self.model.tc_svc_broker_cert_file, + self.model.tc_svc_broker_key_file, ) @cached_property diff --git a/tcex/app/config b/tcex/app/config index 8229f3cc2..9e829dfe2 160000 --- a/tcex/app/config +++ b/tcex/app/config @@ -1 +1 @@ -Subproject commit 8229f3cc21ce6d50979c8dc8b961c7c2ee857585 +Subproject commit 9e829dfe24f68c97e6ea7452a579e7459760f646 diff --git a/tcex/app/key_value_store b/tcex/app/key_value_store index cab3d2870..2c8e730f1 160000 --- a/tcex/app/key_value_store +++ b/tcex/app/key_value_store @@ -1 +1 @@ -Subproject commit cab3d28704444e40b3a5efeb8080d56ebe773f79 +Subproject commit 2c8e730f1ae90adfe2ad6a8642adc1b2bcbef83b diff --git a/tcex/app/playbook b/tcex/app/playbook index 04ba65c86..5033012c4 160000 --- a/tcex/app/playbook +++ b/tcex/app/playbook @@ -1 +1 @@ -Subproject commit 04ba65c8609024b05dea7ee1b41924fefbe65584 +Subproject commit 5033012c4bed258d53f029fcdd22136a868ca82c diff --git a/tcex/exit/exit.py b/tcex/exit/exit.py index db00f02b9..c7225b757 100644 --- a/tcex/exit/exit.py +++ b/tcex/exit/exit.py @@ -55,15 +55,6 @@ def __init__(self, inputs: 'Input'): self._message = None self.log = _logger - def _aot_rpush(self, exit_code: int): - """Push message to AOT action channel.""" - if self.inputs.contents.get('tc_playbook_db_type') == 'Redis': - try: - # pylint: disable=no-member - registry.redis_client.rpush(self.inputs.contents['tc_exit_channel'], exit_code) - except Exception as e: # pragma: no cover - self._exit(ExitCode.FAILURE, f'Exception during AOT exit push ({e}).') - def _exit(self, code: ExitCode | int, msg: str) -> NoReturn: """Exit the App""" code = ExitCode(code) if code is not None else self.code @@ -140,11 +131,6 @@ def exit(self, code: ExitCode | int | None = None, msg: str | None = None) -> No if self.ij.model.is_playbook_app: self.exit_playbook_handler(msg) - # aot notify - if 'tc_aot_enabled' in self.inputs.contents and self.inputs.contents.get('tc_aot_enabled'): - # push exit message - self._aot_rpush(code.value) - # exit token renewal thread if not self.ij.is_external_app: registry.token.shutdown = True @@ -152,30 +138,6 @@ def exit(self, code: ExitCode | int | None = None, msg: str | None = None) -> No # exit self._exit(code, msg) - # TODO: [med] @cblades - is msg required? - # pylint: disable=unused-argument - def exit_aot_terminate(self, code: ExitCode | int | None = None, msg: str | None = None): - """Application exit method with proper exit code only for AOT. - - The method will run the Python standard sys.exit() with the exit code - previously defined via :py:meth:`~tcex.tcex.TcEx.exit_code` or provided - during the call of this method. - - Args: - code: The exit code value for the app. - msg: A message to log and add to message tc output. - """ - code = ExitCode(code) if code is not None else self.code - msg = msg if msg is not None else self.message - - # aot notify - if 'tc_aot_enabled' in self.inputs.contents and self.inputs.contents.get('tc_aot_enabled'): - # push exit message - self._aot_rpush(code.value) - - self.log.info(f'exit-code={code}') - sys.exit(code) - def _exit_msg_handler(self, code: ExitCode, msg: str): """Handle exit message. Write to both log and message_tc.""" if msg is not None: diff --git a/tcex/input/input.py b/tcex/input/input.py index cc918a9a7..9c3b6c671 100644 --- a/tcex/input/input.py +++ b/tcex/input/input.py @@ -10,11 +10,9 @@ # third-party from pydantic import ValidationError # TYPE-CHECKING from pydantic import BaseModel, Extra -from redis import Redis # first-party from tcex.app.config.install_json import InstallJson -from tcex.app.key_value_store import RedisClient from tcex.input.field_type import Sensitive from tcex.input.model.advanced_request_model import AdvancedRequestModel from tcex.input.model.app_external_model import AppExternalModel @@ -75,74 +73,6 @@ def __init__(self, config: dict | None = None, config_file: str | None = None): self.log = _logger self.util = Util() - @staticmethod - def _get_redis_client(host: str, port: int, db: int) -> Redis: - """Return RedisClient client""" - return RedisClient(host=host, port=port, db=db).client - - def _load_aot_params( - self, - tc_aot_enabled: bool, - tc_kvstore_type: str, - tc_kvstore_host: str, - tc_kvstore_port: int, - tc_action_channel: str, - tc_terminate_seconds: int, - ) -> dict[str, dict | list | str]: - """Subscribe to AOT action channel.""" - params = {} - if tc_aot_enabled is not True: - return params - - if not all( - [ - tc_kvstore_type, - tc_kvstore_host, - tc_kvstore_port, - tc_action_channel, - tc_terminate_seconds, - ] - ): - return params - - if tc_kvstore_type == 'Redis': - # get an instance of redis client - redis_client = self._get_redis_client( - host=tc_kvstore_host, - port=tc_kvstore_port, - db=0, - ) - - try: - self.log.info('feature=inputs, event=blocking-for-aot') - msg_data = redis_client.blpop( - keys=tc_action_channel, - timeout=int(tc_terminate_seconds), - ) - - if msg_data is None: # pragma: no cover - # send exit to tcex.exit method - registry.exit.exit_aot_terminate( - code=1, msg='AOT subscription timeout reached.' - ) - else: - msg_data = json.loads(msg_data[1]) - msg_type = msg_data.get('type', 'terminate') - if msg_type == 'execute': - params = msg_data.get('params', {}) - elif msg_type == 'terminate': - # send exit to tcex.exit method - registry.exit.exit_aot_terminate( - code=0, msg='Received AOT terminate message.' - ) - except Exception as e: # pragma: no cover - # send exit to tcex.exit method - registry.exit.exit_aot_terminate( - code=1, msg=f'Exception during AOT subscription ({e}).' - ) - - return params - def _load_config_file(self): """Load config file params provided passed to inputs.""" # default file contents @@ -242,31 +172,6 @@ def contents(self) -> dict: # file params _contents.update(self._load_file_params()) - # aot params - must be loaded last so that it has the kv store channels - tc_aot_enabled = self.util.to_bool(_contents.get('tc_aot_enabled', False)) - if tc_aot_enabled is True: - tc_kvstore_type = _contents.get('tc_kvstore_type') - tc_kvstore_host = _contents.get('tc_kvstore_host') - tc_kvstore_port = _contents.get('tc_kvstore_port') - tc_action_channel = _contents.get('tc_action_channel') - tc_terminate_seconds = _contents.get('tc_terminate_seconds') - - if ( - tc_kvstore_type - and tc_kvstore_host - and tc_kvstore_port - and tc_action_channel - and tc_terminate_seconds - ): - param_data = self._load_aot_params( - tc_aot_enabled=tc_aot_enabled, - tc_kvstore_type=tc_kvstore_type, - tc_kvstore_host=tc_kvstore_host, - tc_kvstore_port=tc_kvstore_port, - tc_action_channel=tc_action_channel, - tc_terminate_seconds=tc_terminate_seconds, - ) - _contents.update(param_data) return _contents @cached_property @@ -328,9 +233,9 @@ def contents_resolved(self) -> dict: # TODO: [high] - can this be replaced with a pydantic root validator? def contents_update(self, inputs: dict): - """Update inputs provided by AOT to be of the proper value and type.""" + """Update inputs provided by core to be of the proper value and type.""" for name, value in inputs.items(): - # ThreatConnect AOT params could be updated in the future to proper JSON format. + # ThreatConnect params could be updated in the future to proper JSON format. # MultiChoice data should be represented as JSON array and Boolean values should be a # JSON boolean and not a string. param = self.ij.model.get_param(name) diff --git a/tcex/input/model/aot_execution_enabled_model.py b/tcex/input/model/aot_execution_enabled_model.py deleted file mode 100644 index ab0620534..000000000 --- a/tcex/input/model/aot_execution_enabled_model.py +++ /dev/null @@ -1,34 +0,0 @@ -"""TcEx Framework Module""" -# third-party -from pydantic import BaseModel, Field - - -class AotExecutionEnabledModel(BaseModel): - """AOT Execution Enabled Model - - Feature: aotExecutionEnabled - - Supported for the following runtimeLevel: - * Playbook - """ - - tc_action_channel: str = Field( - None, - description='The AOT channel to monitor for incoming action.', - inclusion_reason='feature (aotExecutionEnabled)', - ) - tc_aot_enabled: bool = Field( - False, - description='Flag to enable AOT mode for the App.', - inclusion_reason='feature (aotExecutionEnabled)', - ) - tc_exit_channel: str = Field( - None, - description='The AOT channel to send the exit message.', - inclusion_reason='feature (aotExecutionEnabled)', - ) - tc_terminate_seconds: int = Field( - None, - description='The max number of second to wait for action message.', - inclusion_reason='feature (aotExecutionEnabled)', - ) diff --git a/tcex/input/model/cert_model.py b/tcex/input/model/cert_model.py new file mode 100644 index 000000000..841b10fef --- /dev/null +++ b/tcex/input/model/cert_model.py @@ -0,0 +1,43 @@ +"""TcEx Framework Module""" +# third-party +from pydantic import BaseModel, Field + + +class CertModel(BaseModel): + """Input Service Model + + Supported runtimeLevel: + * ApiService + * Playbook + * Organization + * TriggerService + * WebhookTriggerService + """ + + # @bcs - for service App these should be required, for any other App type making them + # required would force a min server version of 7.4 + tc_svc_broker_cacert_file: str | None = Field( + None, + description='The Broker SSL CA (full chain) certificate.', + inclusion_reason='runtimeLevel', + ) + tc_svc_broker_cert_file: str | None = Field( + None, + description='The Broker SSL Server certificate.', + inclusion_reason='runtimeLevel', + ) + tc_svc_broker_jks_file: str | None = Field( + 'Unused', + description='Input for Java Apps.', + inclusion_reason='runtimeLevel', + ) + tc_svc_broker_jks_pwd: str | None = Field( + 'Unused', + description='Input for Java Apps.', + inclusion_reason='runtimeLevel', + ) + tc_svc_broker_key_file: str | None = Field( + None, + description='The Broker SSL Key (full chain) certificate.', + inclusion_reason='runtimeLevel', + ) diff --git a/tcex/input/model/common_model.py b/tcex/input/model/common_model.py index 1cf73ff9d..b9564f7ef 100644 --- a/tcex/input/model/common_model.py +++ b/tcex/input/model/common_model.py @@ -3,10 +3,11 @@ # first-party from tcex.input.model.api_model import ApiModel from tcex.input.model.batch_model import BatchModel +from tcex.input.model.cert_model import CertModel from tcex.input.model.logging_model import LoggingModel from tcex.input.model.path_model import PathModel from tcex.input.model.proxy_model import ProxyModel -class CommonModel(ApiModel, BatchModel, LoggingModel, PathModel, ProxyModel): +class CommonModel(ApiModel, BatchModel, CertModel, LoggingModel, PathModel, ProxyModel): """Model Definition""" diff --git a/tcex/input/model/model_map.py b/tcex/input/model/model_map.py index 5426b0339..5c90bd940 100644 --- a/tcex/input/model/model_map.py +++ b/tcex/input/model/model_map.py @@ -2,7 +2,6 @@ # first-party from tcex.input.model.advanced_request_model import AdvancedRequestModel -from tcex.input.model.aot_execution_enabled_model import AotExecutionEnabledModel from tcex.input.model.app_api_service_model import AppApiServiceModel from tcex.input.model.app_external_model import AppExternalModel from tcex.input.model.app_feed_api_service_model import AppFeedApiServiceModel @@ -18,7 +17,6 @@ # define feature to model map feature_map = { - 'aotExecutionEnabled': [AotExecutionEnabledModel], 'appBuilderCompliant': [], # 'advancedRequest': [], 'CALSettings': [CalSettingModel], diff --git a/tcex/input/model/module_app_model.py b/tcex/input/model/module_app_model.py index 139d2204b..b8811b712 100644 --- a/tcex/input/model/module_app_model.py +++ b/tcex/input/model/module_app_model.py @@ -5,6 +5,7 @@ # first-party from tcex.input.model.api_model import ApiModel +from tcex.input.model.cert_model import CertModel from tcex.input.model.path_model import PathModel from tcex.input.model.playbook_common_model import PlaybookCommonModel from tcex.input.model.playbook_model import PlaybookModel @@ -13,7 +14,7 @@ class ModuleAppModel( - ApiModel, PathModel, PlaybookCommonModel, PlaybookModel, ProxyModel, ServiceModel + ApiModel, CertModel, PathModel, PlaybookCommonModel, PlaybookModel, ProxyModel, ServiceModel ): """Model Definition diff --git a/tcex/input/model/playbook_common_model.py b/tcex/input/model/playbook_common_model.py index 535670f44..7dc36d71a 100644 --- a/tcex/input/model/playbook_common_model.py +++ b/tcex/input/model/playbook_common_model.py @@ -36,6 +36,16 @@ class PlaybookCommonModel(BaseModel): description='The KV Store port number.', inclusion_reason='runtimeLevel', ) + tc_kvstore_tls_enabled: bool = Field( + False, + description='If true, KV Store requires SSL connection.', + inclusion_reason='runtimeLevel', + ) + tc_kvstore_tls_port: int = Field( + 6379, + description='The KV Store TLS port number.', + inclusion_reason='runtimeLevel', + ) tc_kvstore_type: str = Field( 'Redis', description='The KV Store type (Redis or TCKeyValueAPI).', diff --git a/tcex/input/model/service_model.py b/tcex/input/model/service_model.py index 585f7a19d..89943970e 100644 --- a/tcex/input/model/service_model.py +++ b/tcex/input/model/service_model.py @@ -15,16 +15,6 @@ class ServiceModel(BaseModel): * WebhookTriggerService """ - tc_svc_broker_cacert_file: str = Field( - None, - description='The Broker SSL CA (full chain) certificate.', - inclusion_reason='runtimeLevel', - ) - tc_svc_broker_cert_file: str = Field( - None, - description='The Broker SSL Server certificate.', - inclusion_reason='runtimeLevel', - ) tc_svc_broker_conn_timeout: int = Field( 60, description='The broker connection startup timeout in seconds.', @@ -36,16 +26,6 @@ class ServiceModel(BaseModel): description='The Broker service hostname.', inclusion_reason='runtimeLevel', ) - tc_svc_broker_jks_file: str | None = Field( - 'Unused', - description='Input for Java Apps.', - inclusion_reason='runtimeLevel', - ) - tc_svc_broker_jks_pwd: str | None = Field( - 'Unused', - description='Input for Java Apps.', - inclusion_reason='runtimeLevel', - ) tc_svc_broker_port: int = Field( None, description='The Broker service port number.', diff --git a/tests/api/tc/v3/v3_helpers.py b/tests/api/tc/v3/v3_helpers.py index d6d950596..aa11c6438 100644 --- a/tests/api/tc/v3/v3_helpers.py +++ b/tests/api/tc/v3/v3_helpers.py @@ -743,6 +743,19 @@ def obj_api_options(self): # fix discrepancy between /fields and names = ['createdBy'] + if self.v3_helper.v3_object in ['groups', 'indicators'] and 'externalDates' in names: + names = [ + 'externalDateAdded', + 'externalDateExpires', + 'externalLastModified', + ] + + if self.v3_helper.v3_object in ['groups', 'indicators'] and 'sightings' in names: + names = [ + 'firstSeen', + 'lastSeen', + ] + if self.v3_helper.v3_object == 'indicators': if 'genericCustomIndicatorValues' in names: # fix discrepancy between /fields and diff --git a/tests/app/config/apps/tcpb/app_1/app.yaml b/tests/app/config/apps/tcpb/app_1/app.yaml index 6f0f2dc26..bc11279f8 100644 --- a/tests/app/config/apps/tcpb/app_1/app.yaml +++ b/tests/app/config/apps/tcpb/app_1/app.yaml @@ -4,7 +4,6 @@ app: appName: TCPB_-_TcEx_App_1 displayName: TCPB_-_TcEx_App_1 features: - - aotExecutionEnabled - appBuilderCompliant - fileParams - layoutEnabledApp diff --git a/tests/app/config/apps/tcpb/app_bad_install_json/install-template.json b/tests/app/config/apps/tcpb/app_bad_install_json/install-template.json index ac4a7a49a..692bd8450 100644 --- a/tests/app/config/apps/tcpb/app_bad_install_json/install-template.json +++ b/tests/app/config/apps/tcpb/app_bad_install_json/install-template.json @@ -5,7 +5,6 @@ "appId": "7d1a3338-59ef-59a9-a41e-ae853af5988f", "displayName": "Playbook App", "features": [ - "aotExecutionEnabled", "appBuilderCompliant", "fileParams", "layoutEnabledApp", diff --git a/tests/app/playbook/test_playbook_aot.py b/tests/app/playbook/test_playbook_aot.py deleted file mode 100644 index 8c47da5aa..000000000 --- a/tests/app/playbook/test_playbook_aot.py +++ /dev/null @@ -1,97 +0,0 @@ -"""TcEx Framework Module""" - -# third-party -from pydantic import BaseModel - -# first-party -from tcex.input.field_type import String - -# from collections.abc import Callable -# from _pytest.monkeypatch import MonkeyPatch -# from redis import Redis - - -class InputModel(BaseModel): - """.""" - - my_bool: bool - my_multi: list[String] - - -class TestPlaybookAot: - """Test TcEx Playbook AOT Feature.""" - - # @staticmethod - # def test_aot_inputs(playbook_app: Callable[..., MockApp], redis_client: Redis): - # """Test AOT input method of TcEx - - # ..important:: This method duplicates some of the same coverage in inputs module testing. - # """ - # # add AOT setting to App - # config_data = { - # 'tc_action_channel': f'pytest-action-channel-{randint(1000,9999)}', - # 'tc_aot_enabled': True, - # 'tc_exit_channel': f'pytest-exit-channel-{randint(1000,9999)}', - # 'tc_terminate_seconds': 60, - # } - # app = playbook_app(config_data=config_data) - - # # send redis rpush AOT message - # aot_config_data = {'my_bool': 'true', 'my_multi': 'one|two'} - # aot_config_data.update(app.config_data) - # aot_msg = {'type': 'execute', 'params': aot_config_data} - # redis_client.rpush(config_data.get('tc_action_channel'), json.dumps(aot_msg)) - - # # get a configured instance of tcex, missing AOT values - # # tcex will block, check for the AOT method, parse new config, and then run - # tcex = app.tcex - # tcex.inputs.add_model(TestInputModel) - - # assert tcex.inputs.model.my_bool is True - # assert tcex.inputs.model.my_multi == ['one', 'two'] - - # tcex.exit_service._aot_rpush(0) - - # @staticmethod - # def test_aot_terminate(playbook_app: Callable[..., MockApp], redis_client: Redis): - # """Test AOT handling terminate command""" - # # add AOT setting to App - # config_data = { - # 'tc_action_channel': f'pytest-action-channel-{randint(1000,9999)}', - # 'tc_aot_enabled': True, - # 'tc_exit_channel': f'pytest-exit-channel-{randint(1000,9999)}', - # 'tc_terminate_seconds': 60, - # } - # app = playbook_app(config_data=config_data) - - # # send redis rpush AOT message - # aot_msg = {'type': 'terminate'} - # redis_client.rpush(config_data.get('tc_action_channel'), json.dumps(aot_msg)) - - # # get a configured instance of tcex, missing AOT values - # # tcex will block, check for the AOT method, parse new config, and then run - # try: - # app.tcex # pylint: disable=pointless-statement - # # the app will immediately exit since terminate command was already sent - # assert False - # except SystemExit: - # assert True - - # @staticmethod - # def test_aot_timeout(playbook_app: Callable[..., MockApp]): - # """Test AOT timeout waiting on push.""" - # # add AOT setting to App - # config_data = { - # 'tc_action_channel': f'pytest-action-channel-{randint(1000,9999)}', - # 'tc_aot_enabled': True, - # 'tc_exit_channel': f'pytest-exit-channel-{randint(1000,9999)}', - # 'tc_terminate_seconds': 1, - # } - # app = playbook_app(config_data=config_data) - - # try: - # app.tcex.inputs.model # pylint: disable=pointless-statement - # # the app will timeout and exit - # assert False - # except SystemExit: - # assert True diff --git a/tests/input/test_input.py b/tests/input/test_input.py index 98a78037b..822eaba03 100644 --- a/tests/input/test_input.py +++ b/tests/input/test_input.py @@ -4,17 +4,10 @@ import os from collections.abc import Callable from pathlib import Path -from random import randint from uuid import uuid4 -# third-party -import redis -from _pytest.monkeypatch import MonkeyPatch -from pydantic import BaseModel, Extra - # first-party from tcex import TcEx -from tcex.input.input import Input from tcex.registry import registry from tests.mock_app import MockApp @@ -22,49 +15,6 @@ class TestInputsConfig: """Test TcEx Inputs Config.""" - @staticmethod - def test_aot_inputs( - playbook_app: Callable[..., MockApp], redis_client: redis.Redis, monkeypatch: MonkeyPatch - ): - """Test AOT input method of TcEx""" - registry._reset() - - class PytestModel(BaseModel): - """Test Model for Inputs""" - - my_bool: bool - my_multi: list - - class Config: - """.""" - - extra = Extra.allow - - # add AOT setting to App - config_data = { - 'tc_action_channel': f'pytest-action-channel-{randint(1000,9999)}', - 'tc_aot_enabled': True, - } - app = playbook_app(config_data=config_data) - - # ensure inputs object has same FakeRedis instance as the one we will push data to - monkeypatch.setattr(Input, '_get_redis_client', lambda *args, **kwargs: redis_client) - - # send redis rpush AOT message - aot_config_data = {'my_bool': 'true', 'my_multi': 'one|two'} - aot_config_data.update(app.config_data) - aot_msg = {'type': 'execute', 'params': aot_config_data} - redis_client.rpush(config_data['tc_action_channel'], json.dumps(aot_msg)) - - # get a configured instance of tcex, missing AOT values - # tcex will block, check for the AOT method, parse new config, and then run - tcex = app.tcex - tcex.inputs.add_model(PytestModel) # type: ignore - - # print(tcex.inputs.model.json(indent=2)) - assert tcex.inputs.model.my_bool is True # type: ignore - assert tcex.inputs.model.my_multi == ['one', 'two'] # type: ignore - @staticmethod def test_config_kwarg(): """Test config file input method of TcEx""" diff --git a/tests/mock_app.py b/tests/mock_app.py index 5fa9b793b..b13c79a30 100644 --- a/tests/mock_app.py +++ b/tests/mock_app.py @@ -85,8 +85,6 @@ def _config_common(self) -> dict[str, bool | str | None]: def _config_playbook(self) -> dict[str, bool | int | str]: """Return config data for mocked App.""" return { - 'tc_action_channel': 'action-channel', - 'tc_aot_enabled': False, 'tc_exit_channel': 'exit-channel', 'tc_terminate_seconds': 30, } @@ -136,7 +134,6 @@ def _ij_common(self) -> dict[str, Any]: 'displayName': self.ijd.get('display_name') or 'Pytest', 'features': self.ijd.get('features', []) or [ - 'aotExecutionEnabled', 'appBuilderCompliant', 'layoutEnabledApp', 'fileParams',