From b8b807221b2360e1870bc1f8da688e60f7a47d97 Mon Sep 17 00:00:00 2001 From: Artem Batalov Date: Fri, 9 Feb 2024 18:07:04 +0300 Subject: [PATCH] DPNLPF-2192: Outgoing message validation --- smart_kit/message/as_is_to_message.py | 7 +++++++ smart_kit/message/validators/__init__.py | 2 +- .../validators/json_schema_validator.py | 12 ++++++++++-- smart_kit/start_points/base_main_loop.py | 4 ++-- smart_kit/testing/local.py | 2 +- smart_kit/testing/suite.py | 18 ++++++++++++++---- 6 files changed, 35 insertions(+), 10 deletions(-) diff --git a/smart_kit/message/as_is_to_message.py b/smart_kit/message/as_is_to_message.py index 2ba0d2e4..99c110ab 100644 --- a/smart_kit/message/as_is_to_message.py +++ b/smart_kit/message/as_is_to_message.py @@ -5,6 +5,13 @@ class AsIsToMessage(SmartAppToMessage): + @property + def message_name(self) -> str: + return self.command.name + + @property + def incremental_id(self) -> int: + return self.incoming_message.incremental_id @cached_property def as_dict(self): diff --git a/smart_kit/message/validators/__init__.py b/smart_kit/message/validators/__init__.py index 3f4a4722..29dccd19 100644 --- a/smart_kit/message/validators/__init__.py +++ b/smart_kit/message/validators/__init__.py @@ -9,5 +9,5 @@ def get_validators_from_settings(validator_group: str, settings: Settings, model: SmartAppModel) \ -> List[BaseMessageValidator]: validators = settings["template_settings"].get("validators", {}) - return [dynamic_import_object(e["class"])(resources=model.resources, **e["params"]) + return [dynamic_import_object(e["class"])(validator_group=validator_group, resources=model.resources, **e["params"]) for e in validators.get(validator_group, [])] diff --git a/smart_kit/message/validators/json_schema_validator.py b/smart_kit/message/validators/json_schema_validator.py index c6418b60..607c095a 100644 --- a/smart_kit/message/validators/json_schema_validator.py +++ b/smart_kit/message/validators/json_schema_validator.py @@ -1,13 +1,15 @@ from __future__ import annotations from pathlib import Path -from typing import Callable, TYPE_CHECKING +from typing import Callable, TYPE_CHECKING, Optional import jsonschema from jsonschema.validators import RefResolver from core.message.message import SmartAppMessage from core.logging.logger_utils import log +from core.message.validators.base_validator import OnException from smart_kit.message.validators.base_validator_with_resources import BaseMessageValidatorWithResources +from smart_kit.resources import SmartAppResources if TYPE_CHECKING: from core.message.from_message import SmartAppFromMessage @@ -16,10 +18,16 @@ class JSONSchemaValidator(BaseMessageValidatorWithResources): VALIDATOR_EXCEPTION = jsonschema.ValidationError + def __init__(self, validator_group: str = "incoming", + resources: Optional[SmartAppResources] = None, + on_exception: OnException = "raise", *args, **kwargs): + self._message_type = validator_group if validator_group in ("incoming", "outgoing") else "incoming" + super().__init__(resources, on_exception, *args, **kwargs) + def _update_resources(self): self._store = self.resources.get("payload_schema") self._schemas = {Path(k).stem: self._compile_schema(k, v) for k, v in self._store.items() - if len(Path(k).parents) == 1} + if k.startswith(f"/{self._message_type}/")} def _compile_schema(self, uri: str, schema: dict) -> Callable[[dict], None]: cls = jsonschema.validators.validator_for(schema) diff --git a/smart_kit/start_points/base_main_loop.py b/smart_kit/start_points/base_main_loop.py index a7406a26..62f9064f 100644 --- a/smart_kit/start_points/base_main_loop.py +++ b/smart_kit/start_points/base_main_loop.py @@ -46,8 +46,8 @@ def __init__( self.postprocessor = postprocessor_cls() self.db_adapter = self.get_db() self.is_work = True - self.from_msg_validators = get_validators_from_settings("from", settings, model) - self.to_msg_validators = get_validators_from_settings("to", settings, model) + self.from_msg_validators = get_validators_from_settings("incoming", settings, model) + self.to_msg_validators = get_validators_from_settings("outgoing", settings, model) template_settings = self.settings["template_settings"] save_tries = template_settings.get("user_save_collisions_tries", 0) diff --git a/smart_kit/testing/local.py b/smart_kit/testing/local.py index 8a495993..7edde93e 100644 --- a/smart_kit/testing/local.py +++ b/smart_kit/testing/local.py @@ -145,7 +145,7 @@ def process_message(self, raw_message: Dict, headers: tuple = ()) -> Tuple[Any, masking_fields = self.settings["template_settings"].get("masking_fields") message = SmartAppFromMessage( raw_message, headers=headers, masking_fields=masking_fields, - validators=get_validators_from_settings("from", self.settings, self.app_model) + validators=get_validators_from_settings("incoming", self.settings, self.app_model) ) user = self.__user_cls(self.environment.user_id, message, self.user_data, self.settings, self.app_model.scenario_descriptions, diff --git a/smart_kit/testing/suite.py b/smart_kit/testing/suite.py index 99d4e08e..e74a78a1 100644 --- a/smart_kit/testing/suite.py +++ b/smart_kit/testing/suite.py @@ -211,6 +211,9 @@ async def _run(self) -> bool: callbacks = {} app_callback_id = None + + incoming_message_validators = get_validators_from_settings("incoming", self.settings, self.app_model) + outgoing_message_validators = get_validators_from_settings("outgoing", self.settings, self.app_model) for index, raw_msg in enumerate(self.messages): print('Шаг', index) if index and self.interactive: @@ -239,7 +242,7 @@ async def _run(self) -> bool: message = self.create_message( request, headers=headers, - validators=get_validators_from_settings("from", self.settings, self.app_model) + validators=incoming_message_validators ) if not message.validate(): @@ -255,7 +258,14 @@ async def _run(self) -> bool: self.post_setup_user(user) commands = [command async for command in self.app_model.answer(message, user)] - answers = self._generate_answers(user=user, commands=commands, message=message) + answers = self._generate_answers(user=user, commands=commands, message=message, + validators=outgoing_message_validators) + + for answer in answers: + if not answer.validate(): + print(f"[!] Outgoing message {answer.message_name} validation failed.") + return False + answers.extend(self._generate_history_answers(user, message)) predefined_fields_resp = response.get("predefined_fields") @@ -285,7 +295,7 @@ async def run(self) -> bool: finally: self.finalize() - def _generate_answers(self, user, commands, message): + def _generate_answers(self, user, commands, message, validators): answers = [] commands = commands or [] @@ -293,7 +303,7 @@ def _generate_answers(self, user, commands, message): for command in commands: request = SmartKitKafkaRequest(id=None, items=command.request_data) - answer = SmartAppToMessage(command=command, message=message, request=request) + answer = SmartAppToMessage(command=command, message=message, request=request, validators=validators) answers.append(answer) return answers