diff --git a/.changelog/4359.yml b/.changelog/4359.yml new file mode 100644 index 00000000000..0d91a514748 --- /dev/null +++ b/.changelog/4359.yml @@ -0,0 +1,4 @@ +changes: +- description: Moved PB108 to the new validate format. The validate checks that the 'taskid' and the 'id' under the 'task' field are from UUID format. + type: internal +pr_number: 4359 diff --git a/demisto_sdk/commands/validate/sdk_validation_config.toml b/demisto_sdk/commands/validate/sdk_validation_config.toml index 7db20a08e59..a0cbf2f95ba 100644 --- a/demisto_sdk/commands/validate/sdk_validation_config.toml +++ b/demisto_sdk/commands/validate/sdk_validation_config.toml @@ -29,7 +29,7 @@ select = [ "IN100", "IN101", "IN102", "IN104", "IN106", "IN107", "IN108", "IN109", "IN110", "IN112", "IN113", "IN114", "IN115", "IN117", "IN118", "IN121", "IN122", "IN123", "IN124", "IN125", "IN126", "IN127", "IN130", "IN131", "IN134", "IN135", "IN139", "IN141", "IN142", "IN144", "IN145", "IN146", "IN149", "IN150", "IN151", "IN152", "IN153", "IN154", "IN156", "IN158", "IN159", "IN160", "IN161", "IN162", - "PB100", "PB101","PB103","PB104", "PB105", "PB118", "PB123", "PB126", + "PB100", "PB101","PB103","PB104", "PB105","PB108", "PB118", "PB123", "PB126", "DO100", "DO101", "DO102", "DO103", "DO104", "DO105", "DO106", "DS100", "DS101", "DS105", "DS106", "DS107", "SC100", "SC105", "SC106", "SC109", @@ -52,7 +52,7 @@ select = [ "IN131", "IN134", "IN135", "IN139", "IN141", "IN142", "IN144", "IN145", "IN146", "IN149", "IN150", "IN151", "IN152", "IN153", "IN154", "IN156", "IN158", "IN159", "IN160", "IN161", "IN162", "LO107", - "PB100", "PB101","PB103","PB104", "PB105", "PB123", "PB126", + "PB100", "PB101","PB103","PB104", "PB105","PB108", "PB123", "PB126", "BA100", "BA101", "BA105", "BA106", "BA110", "BA111", "BA113", "BA116", "BA118", "BA119", "BA126", "DS100", "DS101", "DS105", "PA100", "PA101", "PA102", "PA103", "PA104", "PA105", "PA107", "PA108", "PA109", "PA111", "PA113", "PA115", "PA117", "PA118", "PA119", "PA120", diff --git a/demisto_sdk/commands/validate/tests/PB_validators_test.py b/demisto_sdk/commands/validate/tests/PB_validators_test.py index 9640d34d034..0ec6d5df4e6 100644 --- a/demisto_sdk/commands/validate/tests/PB_validators_test.py +++ b/demisto_sdk/commands/validate/tests/PB_validators_test.py @@ -18,6 +18,9 @@ from demisto_sdk.commands.validate.validators.PB_validators.PB105_playbook_delete_context_all import ( PlaybookDeleteContextAllValidator, ) +from demisto_sdk.commands.validate.validators.PB_validators.PB108_is_valid_task_id import ( + IsValidTaskIdValidator, +) from demisto_sdk.commands.validate.validators.PB_validators.PB118_is_input_key_not_in_tasks import ( IsInputKeyNotInTasksValidator, ) @@ -232,6 +235,61 @@ def test_IsAskConditionHasUnhandledReplyOptionsValidator(): assert IsAskConditionHasUnhandledReplyOptionsValidator().is_valid([playbook]) +def create_invalid_playbook(field: str): + """Create an invalid playbook that has an invalid taskid or the 'id' under the 'task' field is invalid + Args: + - field str: which field to update taskid or task.id. + Return: + - a playbook object + """ + playbook = create_playbook_object() + tasks = playbook.tasks + for task_id in tasks: + task_obj = tasks[task_id] + if field == "taskid": + task_obj.taskid = task_obj.taskid + "1234" + else: + task_obj.task.id = task_obj.task.id + "1234" + break + return playbook + + +def test_IsValidTaskIdValidator(playbook): + """ + Given: + - A playbook + Case 1: The playbook is valid. + Case 2: The playbook isn't valid, it has invalid taskid. + Case 3: The playbook isn't valid, the 'id' under the 'task' field is invalid. + + When: + - calling IsValidTaskIdValidator.is_valid. + + Then: + - The results should be as expected: + Case 1: The playbook is valid + Case 2: The playbook is invalid + Case 3: The playbook is invalid + """ + # Case 1 + playbook_valid = create_playbook_object() + results_valid = IsValidTaskIdValidator().is_valid([playbook_valid]) + + # Case 2 + playbook_invalid_taskid = create_invalid_playbook("taskid") + results_invalid_taskid = IsValidTaskIdValidator().is_valid( + [playbook_invalid_taskid] + ) + + # Case 3 + playbook_invalid_id = create_invalid_playbook("id") + results_invalid_id = IsValidTaskIdValidator().is_valid([playbook_invalid_id]) + + assert not results_valid + assert results_invalid_taskid + assert results_invalid_id + + def test_PlaybookDeleteContextAllValidator(): """ Given: diff --git a/demisto_sdk/commands/validate/validators/PB_validators/PB108_is_valid_task_id.py b/demisto_sdk/commands/validate/validators/PB_validators/PB108_is_valid_task_id.py new file mode 100644 index 00000000000..35e73f6d9c4 --- /dev/null +++ b/demisto_sdk/commands/validate/validators/PB_validators/PB108_is_valid_task_id.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +from typing import Iterable, List + +from demisto_sdk.commands.common.tools import is_string_uuid +from demisto_sdk.commands.content_graph.objects.base_playbook import TaskConfig +from demisto_sdk.commands.content_graph.objects.playbook import Playbook +from demisto_sdk.commands.validate.validators.base_validator import ( + BaseValidator, + ValidationResult, +) + +ContentTypes = Playbook + + +class IsValidTaskIdValidator(BaseValidator[ContentTypes]): + error_code = "PB108" + description = "Validate that the task ID and the 'id' under the 'task' field are from UUID format." + rationale = "Each task should have a unique id in UUID format to avoid unknown behavior and breaking the playbook." + error_message = "This playbook has tasks with invalid 'taskid' or invalid 'id' under the 'task' field.\n{0}" + related_field = "taskid" + is_auto_fixable = False + + def is_valid(self, content_items: Iterable[ContentTypes]) -> List[ValidationResult]: + """Check whether all playbook tasks has valid taskid and the 'id' under the 'task' field is valid as well. + Args: + - content_items (Iterable[ContentTypes]): The content items to check. + Return: + - List[ValidationResult]. List of ValidationResults objects. + """ + results: List[ValidationResult] = [] + for content_item in content_items: + invalid_tasks = self.invalid_tasks(content_item.tasks) + tasks_error_message = "" + for task_id in invalid_tasks: + tasks_error_message = ( + f"{tasks_error_message}Task {task_id} has invalid UUIDs in the fields " + f"{invalid_tasks[task_id]}.\n" + ) + if invalid_tasks: + results.append( + ValidationResult( + validator=self, + message=self.error_message.format(tasks_error_message), + content_object=content_item, + ) + ) + return results + + def invalid_tasks(self, tasks: dict[str, TaskConfig]) -> dict[str, list]: + """Check which tasks has invalid taskid or the 'id' under the 'task' field is invalid + Args: + - tasks dict[str, TaskConfig]: The playbook tasks. + Return: + - dict[str, TaskConfig] that contains the invalid tasks. + """ + invalid_tasks = {} + for task_id, task in tasks.items(): + taskid = task.taskid + inner_id = task.task.id + is_valid_taskid = is_string_uuid(taskid) + is_valid_inner_id = is_string_uuid(inner_id) + invalid_fields = [] + if not is_valid_taskid: + invalid_fields.append("taskid") + if not is_valid_inner_id: + invalid_fields.append("the 'id' under the 'task' field") + if invalid_fields: + invalid_tasks[task_id] = invalid_fields + return invalid_tasks