Skip to content

Commit

Permalink
Merge cfa25a1 into a5d045c
Browse files Browse the repository at this point in the history
  • Loading branch information
israelpoli committed Apr 1, 2024
2 parents a5d045c + cfa25a1 commit 9d80177
Show file tree
Hide file tree
Showing 8 changed files with 345 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class IndicatorIncidentField(ContentItem):
content: bool = Field(None, exclude=True)
system: bool = Field(None, exclude=True)
group: int = Field(None, exclude=True)
unsearchable: Optional[bool] = Field(None, exclude=True)
version: Optional[int] = 0

def _upload(
Expand Down
8 changes: 7 additions & 1 deletion demisto_sdk/commands/content_graph/parsers/incident_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ def __init__(

@cached_property
def field_mapping(self):
super().field_mapping.update({"object_id": "id", "cli_name": "cliName"})
super().field_mapping.update(
{"object_id": "id", "cli_name": "cliName", "unsearchable": "unsearchable"}
)
return super().field_mapping

@property
Expand All @@ -52,6 +54,10 @@ def supported_marketplaces(self) -> Set[MarketplaceVersions]:
MarketplaceVersions.XSOAR_ON_PREM,
}

@property
def unsearchable(self) -> Optional[bool]:
return get_value(self.json_data, self.field_mapping.get("unsearchable", ""))

def connect_to_dependencies(self) -> None:
"""Collects incident types used as optional dependencies, and scripts as mandatory dependencies."""
for associated_type in set(self.json_data.get("associatedTypes") or []):
Expand Down
2 changes: 1 addition & 1 deletion demisto_sdk/commands/validate/sdk_validation_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ select = [
"RM104", "RM105", "RM109", "RM113", "RM114",
"CL100",
"GF100", "GF101", "GF102",
"IF100", "IF101", "IF102", "IF103", "IF104", "IF105", "IF106",
"IF100", "IF101", "IF102", "IF103", "IF104", "IF105", "IF106", "IF111", "IF113", "IF115",
"IM100", "IM108", "IM109",
"RP101", "BC106", "BC107"
]
Expand Down
174 changes: 172 additions & 2 deletions demisto_sdk/commands/validate/tests/IF_validators_test.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from typing import List
from typing import List, Optional, Union

import pytest

from demisto_sdk.commands.content_graph.objects.incident_field import IncidentField
from demisto_sdk.commands.validate.tests.test_tools import create_incident_field_object
from demisto_sdk.commands.validate.tests.test_tools import (
REPO,
create_incident_field_object,
create_old_file_pointers,
)
from demisto_sdk.commands.validate.validators.IF_validators.IF100_is_valid_name_and_cli_name import (
IsValidNameAndCliNameValidator,
)
Expand All @@ -28,6 +32,17 @@
INCIDENT_PROHIBITED_CLI_NAMES,
IsCliNameReservedWordValidator,
)
from demisto_sdk.commands.validate.validators.IF_validators.IF111_is_field_type_changed import (
IsFieldTypeChangedValidator,
)
from demisto_sdk.commands.validate.validators.IF_validators.IF113_name_field_prefix import (
PACKS_IGNORE,
NameFieldPrefixValidator,
)
from demisto_sdk.commands.validate.validators.IF_validators.IF115_unsearchable_key import (
UnsearchableKeyValidator,
)
from TestSuite.test_tools import ChangeCWD


@pytest.mark.parametrize(
Expand Down Expand Up @@ -190,6 +205,144 @@ def test_IsCliNameReservedWordValidator_not_valid(reserved_word):
)


def test_IsFieldTypeChangedValidator_is_valid():
"""
Given:
- IncidentFiled content items
When:
- run is_valid method
Then:
- Ensure that the ValidationResult returned
for the IncidentField whose 'type' field has changed
- Ensure that no ValidationResult returned when 'type' field has not changed
"""
# not valid
content_item = create_incident_field_object(["type"], ["html"])
old_content_items = [create_incident_field_object(["type"], ["short text"])]
create_old_file_pointers([content_item], old_content_items)
assert IsFieldTypeChangedValidator().is_valid([content_item])

# valid
content_item.field_type = "short text"
assert not IsFieldTypeChangedValidator().is_valid([content_item])


@pytest.mark.parametrize("unsearchable", (False, None))
def test_UnsearchableKeyValidator_is_valid(unsearchable: bool):
"""
Given:
- IncidentFiled content items
When:
- run is_valid method
Then:
- Ensure that the ValidationResult returned
for the IncidentField whose 'unsearchable' field is set to false or not or undefined
- Ensure that no ValidationResult returned when unsearchable set to true
"""
# not valid
content_item = create_incident_field_object(
paths=["unsearchable"], values=[unsearchable]
)
assert UnsearchableKeyValidator().is_valid([content_item])

# valid
content_item.unsearchable = True
assert not UnsearchableKeyValidator().is_valid([content_item])


def test_NameFieldPrefixValidator_is_valid_without_item_prefix():
"""
Given:
- IncidentField content items
When:
- run is_valid method
Then:
- Ensure that the ValidationResult returned
for the IncidentField whose prefix name that not start with relevant pack name
"""
# not valid
with ChangeCWD(REPO.path):
content_item = create_incident_field_object(pack_info={"name": "Foo"})
results = NameFieldPrefixValidator().is_valid([content_item])
assert results
assert results[0].message == (
"Field name must start with the relevant pack name or one of the item prefixes found in pack metadata."
"\nFollowing prefixes are allowed for this IncidentField:"
"\nFoo"
)

# valid
content_item.name = "Foo CVE"
assert not NameFieldPrefixValidator().is_valid([content_item])


@pytest.mark.parametrize(
"item_prefix, valid_prefix, expected_allowed_prefixes",
[
pytest.param(
["Foo test", "Test Incident"],
"Foo test CVE",
["Foo", "Foo test", "Test Incident"],
id="itemPrefix is a list",
),
pytest.param(
"Foo test", "Foo test CVE", ["Foo", "Foo test"], id="itemPrefix is a str"
),
pytest.param(None, "Foo CVE", ["Foo"], id="no itemPrefix exists"),
],
)
def test_NameFieldPrefixValidator_is_valid_with_item_prefix(
item_prefix: Optional[Union[List[str], str]],
valid_prefix: str,
expected_allowed_prefixes: str,
):
"""
Given:
- IncidentField content items
When:
- run is_valid method
Then:
- Ensure that the ValidationResult returned
for the IncidentField whose prefix name is not in `itemPrefix`
which is in pack_metadata
- Ensure that no ValidationResult returned when prefix name
is in itemPrefix which is in pack_metadata
"""
# not valid
with ChangeCWD(REPO.path):
content_item = create_incident_field_object(
pack_info={"name": "Foo", "itemPrefix": item_prefix}
)
results = NameFieldPrefixValidator().is_valid([content_item])
assert results
for prefix in expected_allowed_prefixes:
assert prefix in results[0].message

# valid
content_item.name = valid_prefix
assert not NameFieldPrefixValidator().is_valid([content_item])


@pytest.mark.parametrize(
"special_pack",
PACKS_IGNORE
)
def test_NameFieldPrefixValidator_is_valid_with_special_packs(special_pack: str):
"""
Given:
- IncidentField content item whose pack name is one of the special packs
When:
- run is_valid method
Then:
- Ensure that no ValidationResult returned
"""
with ChangeCWD(REPO.path):
content_item = create_incident_field_object(
pack_info={"name": special_pack}
)
assert not NameFieldPrefixValidator().is_valid([content_item])


def test_IsValidContentFieldValidator_valid():
"""
Given:
Expand Down Expand Up @@ -338,3 +491,20 @@ def test_IsValidGroupFieldValidator_fix():
result = IsValidGroupFieldValidator().fix(incident_field)
assert result.message == f"`group` field is set to {REQUIRED_GROUP_VALUE}."
assert incident_field.group == REQUIRED_GROUP_VALUE


def test_IsFieldTypeChangedValidator_fix():
"""
Given:
- IncidentField that its `type` field has changed
When:
- run fix method
Then:
- Ensure the field `type` has changed back
"""
content_item = create_incident_field_object(["type"], ["html"])
old_content_items = [create_incident_field_object(["type"], ["short text"])]
create_old_file_pointers([content_item], old_content_items)
results = IsFieldTypeChangedValidator().fix(content_item)
assert content_item.field_type == "short text"
assert results.message == "Changed the `type` field back to `short text`."
6 changes: 5 additions & 1 deletion demisto_sdk/commands/validate/tests/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,9 @@ def create_incident_type_object(


def create_incident_field_object(
paths: Optional[List[str]] = None, values: Optional[List[Any]] = None
paths: Optional[List[str]] = None,
values: Optional[List[Any]] = None,
pack_info: Optional[Dict[str, Any]] = None,
) -> IncidentField:
"""Creating an incident_field object with altered fields from a default incident_field json structure.
Expand All @@ -364,6 +366,8 @@ def create_incident_field_object(
json_content = load_json("incident_field.json")
update_keys(json_content, paths, values)
pack = REPO.create_pack()
if pack_info:
pack.set_data(**pack_info)
pack.create_incident_field(name="incident_field", content=json_content)
return cast(
IncidentField, BaseContent.from_path(Path(pack.incident_fields[0].path))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from __future__ import annotations

from typing import Iterable, List, cast

from demisto_sdk.commands.common.constants import GitStatuses
from demisto_sdk.commands.content_graph.objects.incident_field import IncidentField
from demisto_sdk.commands.validate.validators.base_validator import (
BaseValidator,
FixResult,
ValidationResult,
)

ContentTypes = IncidentField


class IsFieldTypeChangedValidator(BaseValidator[ContentTypes]):
error_code = "IF111"
description = "Checks if the field type changed."
rationale = "Changing type of IncidentField is not allowed by the platform."
error_message = "Changing incident field type is not allowed."
fix_message = "Changed the `type` field back to `{old_type}`."
related_field = "type"
is_auto_fixable = True
expected_git_statuses = [GitStatuses.MODIFIED]

def is_valid(self, content_items: Iterable[ContentTypes]) -> List[ValidationResult]:
return [
ValidationResult(
validator=self,
message=self.error_message,
content_object=content_item,
)
for content_item in content_items
if (
content_item.field_type
!= cast(ContentTypes, content_item.old_base_content_object).field_type
)
]

def fix(self, content_item: ContentTypes) -> FixResult:
content_item.field_type = cast(
ContentTypes, content_item.old_base_content_object
).field_type
return FixResult(
validator=self,
message=self.fix_message.format(old_type=content_item.field_type),
content_object=content_item,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from __future__ import annotations

from typing import Iterable, List

from demisto_sdk.commands.common.constants import GitStatuses
from demisto_sdk.commands.content_graph.objects.incident_field import IncidentField
from demisto_sdk.commands.validate.validators.base_validator import (
BaseValidator,
ValidationResult,
)

ContentTypes = IncidentField
PACKS_IGNORE = ["Common Types", "Core Alert Fields"]


class NameFieldPrefixValidator(BaseValidator[ContentTypes]):
error_code = "IF113"
description = "Checks if field name starts with its pack name or one of the item prefixes from pack metadata."
rationale = "Required by the platform."
error_message = (
"Field name must start with the relevant pack name or one of the item prefixes found in pack metadata."
"\nFollowing prefixes are allowed for this IncidentField:"
"\n{allowed_prefixes}"
)
related_field = "name"
expected_git_statuses = [GitStatuses.ADDED]

def is_valid(self, content_items: Iterable[ContentTypes]) -> List[ValidationResult]:
return [
ValidationResult(
validator=self,
message=self.error_message.format(
allowed_prefixes=", ".join(allowed_prefixes(content_item))
),
content_object=content_item,
)
for content_item in content_items
if (
content_item.pack_name not in PACKS_IGNORE
and not name_include_allowed_prefix(content_item)
)
]


def name_include_allowed_prefix(content_item: ContentTypes) -> bool:
"""
Check if the IncidentField name begins with any of the allowed prefixes.
"""
for prefix in allowed_prefixes(content_item):
if content_item.name.startswith(prefix):
return True
return False


def allowed_prefixes(content_item: ContentTypes) -> set:
"""
Collects from pack metadata all the allowed prefixes
"""
prefixes = {content_item.pack_name}
if not content_item.in_pack:
return prefixes

metadata = content_item.in_pack.pack_metadata_dict or {}
item_prefix = metadata.get("itemPrefix")

if not item_prefix:
return prefixes
elif isinstance(item_prefix, list):
return prefixes | set(item_prefix)
else:
return prefixes | {item_prefix}
Loading

0 comments on commit 9d80177

Please sign in to comment.