Skip to content
4 changes: 4 additions & 0 deletions .changelog/5095.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
changes:
- description: Added AG105 to validate that the types of arguments and outputs are valid.
type: internal
pr_number: 5095
2 changes: 2 additions & 0 deletions demisto_sdk/commands/validate/sdk_validation_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ select = [
"AG101",
"AG102",
"AG103",
"AG105",
"DS100",
"DS101",
"DS105",
Expand Down Expand Up @@ -323,6 +324,7 @@ select = [
"AG101",
"AG102",
"AG103",
"AG105",
"PA100",
"PA101",
"PA102",
Expand Down
178 changes: 177 additions & 1 deletion demisto_sdk/commands/validate/tests/AG_validators_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from pathlib import Path

from demisto_sdk.commands.content_graph.objects.agentix_action import AgentixAction
from demisto_sdk.commands.content_graph.objects.agentix_action import (
AgentixAction,
AgentixActionArgument,
AgentixActionOutput,
)
from demisto_sdk.commands.content_graph.objects.agentix_agent import AgentixAgent
from demisto_sdk.commands.content_graph.objects.script import Script
from demisto_sdk.commands.validate.validators.AG_validators.AG100_is_forbidden_content_item import (
Expand All @@ -9,6 +13,9 @@
from demisto_sdk.commands.validate.validators.AG_validators.AG101_is_correct_mp import (
IsCorrectMPValidator,
)
from demisto_sdk.commands.validate.validators.AG_validators.AG105_is_valid_types import (
IsTypeValid,
)


def test_is_forbidden_content_item():
Expand Down Expand Up @@ -215,3 +222,172 @@ def test_is_correct_marketplace():
assert results[0].message == (
"The following Agentix related content item 'test' should have only marketplace 'platform'."
)


def test_is_type_valid():
"""
Given
- One AgentixAction with valid argument and output types.
- One AgentixAction with invalid argument and output types.
- One AgentixAction with mixed valid and invalid types.

When
- Calling the IsTypeValid obtain_invalid_content_items function.

Then
- Ensure that 2 validation failures are returned.
- Make sure the error messages include the invalid argument and output names,
and list the valid type options.
"""
# Valid content item
valid_action = AgentixAction(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
valid_action = AgentixAction(
valid_action = create_agentix_action_object(
....

Please try to use create_agentix_action_object() insted.

color="red",
description="",
display="",
path=Path("test_valid.yml"),
marketplaces=["platform"],
name="valid_action",
fromversion="",
toversion="",
display_name="ValidAction",
deprecated=False,
id="",
node_id="",
underlying_content_item_id="test",
underlying_content_item_name="test",
underlying_content_item_type="script",
underlying_content_item_version=-1,
agent_id="test",
args=[
AgentixActionArgument(
name="arg1", description="arg1", type="string", underlyingargname="arg1"
),
AgentixActionArgument(
name="arg2",
description="arg2",
type="boolean",
underlyingargname="arg2",
),
],
outputs=[
AgentixActionOutput(
name="output1",
type="json",
description="output1",
underlyingoutputcontextpath="output1",
),
AgentixActionOutput(
name="output2",
type="number",
description="output2",
underlyingoutputcontextpath="output2",
),
],
)

# Invalid types for both args and outputs
invalid_action = AgentixAction(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
invalid_action = AgentixAction(
invalid_action = create_agentix_action_object(

Same in here.

color="red",
description="",
display="",
path=Path("test_invalid.yml"),
marketplaces=["platform"],
name="invalid_action",
fromversion="",
toversion="",
display_name="InvalidAction",
deprecated=False,
id="",
node_id="",
underlying_content_item_id="test",
underlying_content_item_name="test",
underlying_content_item_type="script",
underlying_content_item_version=-1,
agent_id="test",
args=[
AgentixActionArgument(
name="arg_invalid",
type="InvalidType",
description="arg_invalid",
underlyingargname="arg_invalid",
),
],
outputs=[
AgentixActionOutput(
name="output_invalid",
type="Object",
description="output_invalid",
underlyingoutputcontextpath="output_invalid",
),
],
)

# Mixed valid and invalid
mixed_action = AgentixAction(
color="red",
description="",
display="",
path=Path("test_mixed.yml"),
marketplaces=["platform"],
name="mixed_action",
fromversion="",
toversion="",
display_name="MixedAction",
deprecated=False,
id="",
node_id="",
underlying_content_item_id="test",
underlying_content_item_name="test",
underlying_content_item_type="script",
underlying_content_item_version=-1,
agent_id="test",
args=[
AgentixActionArgument(
name="arg_ok",
type="number",
description="arg_ok",
underlyingargname="arg_ok",
),
AgentixActionArgument(
name="arg_bad",
type="Blob",
description="arg_bad",
underlyingargname="arg_bad",
),
],
outputs=[
AgentixActionOutput(
name="output_ok",
type="string",
description="output_ok",
underlyingoutputcontextpath="output_ok",
),
AgentixActionOutput(
name="output_bad",
type="Array",
description="output_bad",
underlyingoutputcontextpath="output_bad",
),
],
)

content_items = [valid_action, invalid_action, mixed_action]

results = IsTypeValid().obtain_invalid_content_items(content_items)

# We expect 2 invalid results: invalid_action and mixed_action
assert len(results) == 2

# Validate first message content
assert (
"The following Agentix action 'InvalidAction' contains invalid types:\n"
"Arguments with invalid types: arg_invalid. Possible argument types: unknown, keyValue, textArea, string, number, date, boolean.\nOutputs with invalid types: output_invalid. "
"Possible output types: unknown, string, number, date, boolean, json."
) in results[0].message

# Validate second message content
assert (
"The following Agentix action 'MixedAction' contains invalid types:\n"
"Arguments with invalid types: arg_bad. Possible argument types: unknown, keyValue, textArea, string, number, date, boolean.\n"
"Outputs with invalid types: output_bad. Possible output types: unknown, string, number, date, boolean, json."
) in results[1].message
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from typing import Iterable, List, Optional, Set

from demisto_sdk.commands.content_graph.objects import (
AgentixAction,
)
from demisto_sdk.commands.content_graph.objects.agentix_action import (
AgentixActionArgument,
AgentixActionOutput,
)
from demisto_sdk.commands.validate.validators.base_validator import (
BaseValidator,
ValidationResult,
)

ContentTypes = AgentixAction

args_valid_types = [
"unknown",
"keyValue",
"textArea",
"string",
"number",
"date",
"boolean",
]

outputs_valid_types = ["unknown", "string", "number", "date", "boolean", "json"]


class IsTypeValid(BaseValidator[ContentTypes]):
error_code = "AG105"
description = "Ensures that all arguments and outputs use valid data types from the closed list."
rationale = "Helps the LLM understand and process data correctly according to its expected type."
error_message = "The following Agentix action '{0}' contains invalid types:\n{1}"

def obtain_invalid_content_items(
self, content_items: Iterable[ContentTypes]
) -> List[ValidationResult]:
validation_results: List[ValidationResult] = []
for content_item in content_items:
final_message = ""

# Check invalid arguments types
if invalid_args_types := self.is_invalid_args_type(
content_item.args, args_valid_types
):
final_message += (
f"Arguments with invalid types: {', '.join(invalid_args_types)}. "
f"Possible argument types: {', '.join(args_valid_types)}.\n"
)

# Check invalid outputs types
if invalid_outputs_types := self.is_invalid_outputs_type(
content_item.outputs, outputs_valid_types
):
final_message += (
f"Outputs with invalid types: {', '.join(invalid_outputs_types)}. "
f"Possible output types: {', '.join(outputs_valid_types)}."
)

if final_message:
validation_results.append(
ValidationResult(
validator=self,
message=self.error_message.format(
content_item.display_name,
final_message,
),
content_object=content_item,
)
)

return validation_results

def is_invalid_args_type(
self,
elements: Optional[List[AgentixActionArgument]],
valid_types: List[str],
) -> Set[str]:
invalid_element_names: Set[str] = set()
if elements is None:
return invalid_element_names

for element in elements:
if element.type.lower() not in valid_types:
invalid_element_names.add(element.name)

return invalid_element_names

def is_invalid_outputs_type(
self,
elements: Optional[List[AgentixActionOutput]],
valid_types: List[str],
) -> Set[str]:
invalid_element_names: Set[str] = set()
if elements is None:
return invalid_element_names

for element in elements:
if element.type.lower() not in valid_types:
invalid_element_names.add(element.name)

return invalid_element_names