Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: migrates rule validation to pydantic #207

Merged
merged 11 commits into from
Apr 24, 2024
Merged
10 changes: 8 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def invalid_param_rule_data() -> Dict[str, Any]:
const.PROFILE: {
const.DESCRIPTION: "Simple NIST Profile",
const.HREF: "profiles/simplified_nist_profile/profile.json",
const.INCLUDE_CONTROLS: [{"id": "ac-1"}],
},
const.PARAMETER: {
const.NAME: "prm_1",
Expand All @@ -118,7 +119,12 @@ def invalid_param_rule_data() -> Dict[str, Any]:
},
const.DEFAULT_VALUE: "5%",
},
}
},
const.COMPONENT_INFO_TAG: {
const.NAME: "Component 1",
const.TYPE: "service",
const.DESCRIPTION: "Component 1 description",
},
}


Expand Down Expand Up @@ -156,7 +162,7 @@ def test_rule() -> TrestleRule:
parameter=Parameter(
name="test",
description="test",
alternative_values={},
alternative_values={"default": "test", "test": "test"},
default_value="test",
),
check=Check(name="test_check", description="test check"),
Expand Down
9 changes: 7 additions & 2 deletions tests/data/yaml/test_complete_rule.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ x-trestle-rule-info:
parameter:
name: prm_1
description: prm_1 description
alternative-values: {'default': '5%', '5pc': '5%', '10pc': '10%', '15pc': '15%', '20pc': '20%'}
default-value: '5%'
alternative-values:
default: 5%
5pc: 5%
10pc: 10%
15pc: 15%
20pc: 20%
default-value: 5%
check:
name: my_check
description: My check description
Expand Down
8 changes: 6 additions & 2 deletions tests/data/yaml/test_complete_rule_multiple_controls.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ x-trestle-rule-info:
parameter:
name: prm_1
description: prm_1 description
alternative-values: {'default': '5%', '5pc': '5%', '10pc': '10%', '15pc': '15%', '20pc': '20%'}
default-value: '5%'
alternative-values:
default: 5%
5pc: 5%
10pc: 10%
20pc: 20%
default-value: 5%
check:
name: my_check
description: My check description
Expand Down
8 changes: 6 additions & 2 deletions tests/data/yaml/test_incomplete_rule.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ x-trestle-rule-info:
parameter:
name: prm_1
description: prm_1 description
alternative-values: {'default': '5%', '5pc': '5%', '10pc': '10%', '15pc': '15%', '20pc': '20%'}
default-value: '5%'
alternative-values:
default: 5%
5pc: 5%
10pc: 10%
20pc: 20%
default-value: 5%
profile:
description: Simple NIST Profile
href: profiles/simplified_nist_profile/profile.json
Expand Down
8 changes: 5 additions & 3 deletions tests/data/yaml/test_rule_invalid_params.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ x-trestle-rule-info:
parameter:
name: prm_1
description: prm_1 description
alternative-values: {'5pc': '5%', '10pc': '10%', '15pc': '15%', '20pc': '20%'}
default-value: '5%'
alternative-values:
default: 10%
10pc: 10%
20pc: 20%
default-value: 5%
profile:
description: Simple NIST Profile
href: profiles/simplified_nist_profile/profile.json
include-controls:
- id: ac-2
Expand Down
4 changes: 1 addition & 3 deletions tests/trestlebot/tasks/test_rule_transform_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,7 @@ def test_rule_transform_task_with_invalid_rule(tmp_trestle_dir: str) -> None:
tmp_trestle_dir, test_rules_dir, transformer
)

with pytest.raises(
TaskException, match="Failed to transform rule .*: Missing key in YAML file: .*"
):
with pytest.raises(TaskException, match=".*: Missing key in YAML file: .*"):
rule_transform_task.execute()


Expand Down
2 changes: 1 addition & 1 deletion tests/trestlebot/transformers/test_csv_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def test_csv_builder(test_rule: TrestleRule, tmp_trestle_dir: str) -> None:
assert row["Control_Id_List"] == "ac-1 ac-2"
assert row["Parameter_Id"] == test_rule.parameter.name # type: ignore
assert row["Parameter_Description"] == test_rule.parameter.description # type: ignore
assert row["Parameter_Value_Alternatives"] == "{}"
assert row["Parameter_Value_Alternatives"] == '{"default": "test", "test": "test"}'
assert row["Parameter_Value_Default"] == test_rule.parameter.default_value # type: ignore
assert row["Profile_Description"] == test_rule.profile.description
assert row["Profile_Source"] == test_rule.profile.href
Expand Down
48 changes: 0 additions & 48 deletions tests/trestlebot/transformers/test_validations.py

This file was deleted.

47 changes: 33 additions & 14 deletions tests/trestlebot/transformers/test_yaml_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@

"""Test for YAML Transformer."""

import json
import re
from typing import Any, Dict

import pytest

from tests.testutils import YAML_TEST_DATA_PATH
from trestlebot.transformers.base_transformer import RulesTransformerException
from trestlebot.transformers.trestle_rule import TrestleRule
from trestlebot.transformers.validations import ValidationHandler, parameter_validation
from trestlebot.transformers.yaml_transformer import (
FromRulesYAMLTransformer,
ToRulesYAMLTransformer,
Expand All @@ -21,9 +24,9 @@ def test_rule_transformer() -> None:
# load rule from path and close the file
# get the file info as a string
rule_path = YAML_TEST_DATA_PATH / "test_complete_rule.yaml"
rule_file = open(rule_path, "r")
rule_file_info = rule_file.read()
rule_file.close()
rule_file_info: str
with open(rule_path, "r") as rule_file:
rule_file_info = rule_file.read()

transformer = ToRulesYAMLTransformer()
rule = transformer.transform(rule_file_info)
Expand Down Expand Up @@ -69,31 +72,47 @@ def test_rules_transform_with_invalid_rule() -> None:
# load rule from path and close the file
# get the file info as a string
rule_path = YAML_TEST_DATA_PATH / "test_invalid_rule.yaml"
rule_file = open(rule_path, "r")
rule_file_info = rule_file.read()
rule_file.close()
rule_file_info: str
with open(rule_path, "r") as rule_file:
rule_file_info = rule_file.read()
transformer = ToRulesYAMLTransformer()

with pytest.raises(
RulesTransformerException, match="Invalid YAML file: 1 validation error .*"
RulesTransformerException, match=".*value is not a valid dict.*"
):
transformer.transform(rule_file_info)


def test_rules_without_default(invalid_param_rule_data: Dict[str, Any]) -> None:
"""Test rules without default parameter value."""
transformer = ToRulesYAMLTransformer()

json_str = json.dumps(invalid_param_rule_data)
rule = transformer.transform(json_str)
assert "default" in rule.parameter.alternative_values # type: ignore
assert (
rule.parameter.alternative_values.get("default") == rule.parameter.default_value # type: ignore
)


def test_rules_transform_with_additional_validation() -> None:
"""Test rules transform with additional validation."""
# load rule from path and close the file
# get the file info as a string
rule_path = YAML_TEST_DATA_PATH / "test_rule_invalid_params.yaml"
rule_file = open(rule_path, "r")
rule_file_info = rule_file.read()
rule_file.close()
validation_handler_chain = ValidationHandler(parameter_validation)
transformer = ToRulesYAMLTransformer(validation_handler_chain)
rule_file_info: str
with open(rule_path, "r") as rule_file:
rule_file_info = rule_file.read()
transformer = ToRulesYAMLTransformer()

expected_error = """2 error(s) found:
Location: description, Type: value_error.missing, Message: field required
Location: default-value, Type: value_error, Message: Default value 5% must be in the alternative \
values dict_values(['10%', '10%', '20%'])"""

with pytest.raises(
RulesTransformerException,
match=".*Default value must be one of the alternative values",
match=re.escape(expected_error),
):
transformer.transform(rule_file_info)

Expand Down
3 changes: 3 additions & 0 deletions trestlebot/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@
PROFILE = "profile"
HREF = "href"
ALTERNATIVE_VALUES = "alternative-values"
DEFAULT_KEY = "default"
DEFAULT_VALUE = "default-value"
TYPE = "type"
INCLUDE_CONTROLS = "include-controls"

COMPONENT_YAML = "component.yaml"
COMPONENT_INFO_TAG = trestle_const.TRESTLE_TAG + "component-info"
Expand Down
2 changes: 1 addition & 1 deletion trestlebot/entrypoints/entrypoint_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ def handle_exception(
exception: Exception, msg: str = "Exception occurred during execution"
) -> int:
"""Log the exception and return the exit code"""
logger.error(msg + f": {exception}", exc_info=True)
logger.error(msg + f": {exception}")

if isinstance(exception, EntrypointInvalidArgException):
return const.INVALID_ARGS_EXIT_CODE
Expand Down
10 changes: 1 addition & 9 deletions trestlebot/entrypoints/rule_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from trestlebot.entrypoints.log import set_log_level_from_args
from trestlebot.tasks.base_task import ModelFilter, TaskBase
from trestlebot.tasks.rule_transform_task import RuleTransformTask
from trestlebot.transformers.validations import ValidationHandler, parameter_validation
from trestlebot.transformers.yaml_transformer import ToRulesYAMLTransformer


Expand Down Expand Up @@ -56,20 +55,13 @@ def run(self, args: argparse.Namespace) -> None:
try:
set_log_level_from_args(args)

# Configure the YAML Transformer for the task
validation_handler: ValidationHandler = ValidationHandler(
parameter_validation
)
transformer: ToRulesYAMLTransformer = ToRulesYAMLTransformer(
validation_handler
)

# Allow any model to be skipped from the args, by default include all
model_filter: ModelFilter = ModelFilter(
skip_patterns=comma_sep_to_list(args.skip_items),
include_patterns=["*"],
)

transformer = ToRulesYAMLTransformer()
rule_transform_task: RuleTransformTask = RuleTransformTask(
working_dir=args.working_dir,
rules_view_dir=args.rules_view_path,
Expand Down
8 changes: 3 additions & 5 deletions trestlebot/tasks/rule_transform_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,14 @@ def _transform_components(self, component_definition_path: pathlib.Path) -> None
rule = self._rule_transformer.transform(rule_stream)
csv_builder.add_row(rule)
except RulesTransformerException as e:
transformation_errors.append(
f"Failed to transform rule {rule_path.name}: {e}"
)
transformation_errors.append(f"{rule_path.as_posix()}: {e}")

if len(transformation_errors) > 0:
transformation_error_str = "\n".join(transformation_errors)
raise TaskException(
f"Failed to transform rules for component definition {component_definition_path.name}: \
\n{', '.join(transformation_errors)}"
{transformation_error_str}"
)

if csv_builder.row_count == 0:
raise TaskException(
f"No rules found for component definition {component_definition_path.name}"
Expand Down
2 changes: 1 addition & 1 deletion trestlebot/transformers/csv_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def _add_parameter(self, parameter: Parameter) -> Dict[str, str]:
parameter_dict: Dict[str, str] = {
PARAMETER_ID: parameter.name,
PARAMETER_DESCRIPTION: parameter.description,
PARAMETER_VALUE_ALTERNATIVES: f"{parameter.alternative_values}",
PARAMETER_VALUE_ALTERNATIVES: json.dumps(parameter.alternative_values),
PARAMETER_VALUE_DEFAULT: parameter.default_value,
}
return parameter_dict
Expand Down
Loading
Loading